# Security & Compliance

Security architecture, compliance, DPIA, encryption, breach response, key management, testing

# Compliance Framework Document

# Compliance Framework Document

> **Project:** Drop — Fintech Payment App (ALAI Holding AS)
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** ALAI Compliance Team
> **Status:** Draft
> **Reviewers:** DPO, Legal Counsel, CEO
> **Classification:** Confidential

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1     | 2026-02-12 | Compliance Agent (ALAI) | Initial gap analysis and regulatory mapping |
| 1.0     | 2026-02-23 | Security Architect (ALAI) | Framework document |

---

## 1. Applicable Regulations

**Overall Compliance Readiness (MVP stage, 2026-02-13):** 8/100 — Pre-production MVP. No live transactions.

| Regulation | Norwegian Law | Applicability | Status |
|-----------|--------------|--------------|--------|
| PSD2 | Betalingstjenesteloven (LOV-2018-11-23-85) | Core — payment services regulation | 10% ready |
| AML/KYC | Hvitvaskingsloven (LOV-2018-06-01-23) | Core — anti-money laundering | 5% ready |
| GDPR | Personopplysningsloven (LOV-2018-06-15-38) | Core — personal data protection | 15% ready |
| ICT Security | IKT-forskriften / DORA (EU) 2022/2554 | Required for financial enterprises | 25% ready |
| Financial Enterprise | Finansforetaksloven (LOV-2015-04-10-17) | Licensing and governance | 0% ready |
| Currency Registry | Valutaregisterloven (LOV-2004-12-17-109) | Cross-border payment reporting | 0% ready |
| Consumer Protection | Finansavtaleloven (LOV-2020-12-18-146) | User rights | Partial |

**Source:** `legal/drop-regulatory-map-v2.md`, `legal/drop-gap-analysis-v2.md`

**Compliance Owner:** Alem Bašić, CEO/CISO — ALAI Holding AS (alem@alai.no)
**External Auditor:** TBD — requires appointment before license application
**Last Audit:** 2026-02-12 (internal security audit) | **Next Audit:** TBD (prior to license application)

---

## 2. GDPR Compliance

**Source:** `legal/personvernerklaering.md`, `legal/dpia-vurdering.md`, `legal/drop-regulatory-map-v2.md §4`

### 2.1 Requirements Summary

| Article | Requirement | Our Implementation | Status |
|---------|------------|-------------------|--------|
| Art. 5 | Data minimization, purpose limitation | Only collect necessary fields; DPIA documents necessity | Partial |
| Art. 6 | Lawful basis for processing | See §2.2 | Partial |
| Art. 7 | Consent — specific, informed, unambiguous | Consent management TBD | Not implemented |
| Art. 13/14 | Privacy notice at collection | `legal/personvernerklaering.md` (draft, Norwegian) | Draft exists |
| Art. 17 | Right to erasure | TBD — account deletion flow not built | Planned Phase 2 |
| Art. 20 | Right to data portability | TBD — data export feature planned | Planned Phase 2 |
| Art. 25 | Privacy by design and default | Pass-through model minimizes data held | Architectural |
| Art. 30 | Records of processing activities | `legal/behandlingsprotokoll.md` — TBD | Not created |
| Art. 32 | Appropriate security measures | See security-architecture.md | Partial |
| Art. 33 | 72-hour breach notification | See data-breach-response-plan.md | Documented |
| Art. 35 | DPIA for high-risk processing | `legal/dpia-vurdering.md` | Draft exists |
| Art. 37 | DPO designation | TBD — not yet appointed | Not done |
| Art. 44 | Cross-border transfers | SCCs required — see §2.4 | Planned |

### 2.2 Lawful Basis Inventory

| Processing Activity | Lawful Basis | Legal Basis Document | Retention |
|--------------------|-----------|--------------------|---------|
| Account creation and management | Contract (Art. 6.1.b) | `legal/brukervilkar.md` (Terms) | Duration + 2 years |
| Payment initiation (PISP) | Contract (Art. 6.1.b) | `legal/brukervilkar.md` | 5 years (Bokføringsloven) |
| Account info reading (AISP) | Consent (Art. 6.1.a) | Consent at onboarding | Until consent withdrawn |
| AML/KYC identity verification | Legal obligation (Art. 6.1.c) | Hvitvaskingsloven §§ 10-18 | 5 years (hvvl. §30) |
| Transaction monitoring | Legal obligation (Art. 6.1.c) | Hvitvaskingsloven §§ 24-25 | 5 years (hvvl. §30) |
| Fraud detection | Legitimate interest (Art. 6.1.f) | LIA documented in DPIA | 2 years |
| Security logging | Legitimate interest (Art. 6.1.f) | IKT-sikkerhetspolicy | 12-24 months |
| Marketing emails | Consent (Art. 6.1.a) | Consent record | Until consent withdrawn |

### 2.3 Controls Mapping

| Control | Requirement | Status | Evidence |
|---------|------------|--------|---------|
| Privacy notice (Norwegian) | Art. 13/14 | Draft | `legal/personvernerklaering.md` |
| DPIA | Art. 35 | Draft | `legal/dpia-vurdering.md` |
| DPO contact | Art. 37 | Not done | TBD — DPO appointment needed |
| Data breach response plan | Art. 33 | Documented | `docs/SECURITY-COMPLIANCE/data-breach-response-plan.md` |
| Data processing agreements | Art. 28 | Partial | `legal/dpa-sumsub.md`, `dpa-swan.md`, `dpa-sentry.md` |
| SCCs for non-EEA transfers | Art. 46 | Planned | Required for remittance corridors |
| Register of processing activities | Art. 30 | Not created | `legal/behandlingsprotokoll.md` to be completed |

### 2.4 Data Subject Rights — Implementation

| Right | Status | Target Implementation |
|-------|--------|----------------------|
| Access (Subject Access Request) | Not built | `GET /api/users/me/data-export` — Phase 2 |
| Rectification | Partial | `PATCH /api/users/me` — settings update exists |
| Erasure | Not built | Account deletion + anonymization — Phase 2 |
| Portability | Not built | JSON export endpoint — Phase 2 |
| Restriction of processing | Not built | Phase 2 |
| Objection to processing | Not built | Support flow — Phase 2 |

**SLA target:** 30 days per GDPR requirement.

### 2.5 Cross-Border Transfer Compliance

Drop remittance to 30+ countries triggers GDPR Chapter V requirements:

| Transfer | Mechanism | Status |
|---------|-----------|--------|
| Drop → EEA countries (PLN, EUR) | Free flow — no restriction | Compliant |
| Drop → UK | Adequacy decision | Compliant |
| Drop → Serbia (RSD) | SCCs + Transfer Impact Assessment | Planned |
| Drop → Bosnia-Herzegovina (BAM) | SCCs + TIA | Planned |
| Drop → Turkey (TRY) | SCCs + TIA | Planned |
| Drop → Pakistan (PKR) | SCCs + TIA + supplementary measures | Planned — high risk |

**Data minimized in transfer:** Only sender name, recipient name/account, amount, currency, reference. Fødselsnummer NEVER transferred cross-border.

**Source:** `legal/dpia-vurdering.md §7`

---

## 3. PSD2 / SCA Compliance

**Source:** `legal/drop-regulatory-map-v2.md §2`, `legal/drop-gap-analysis-v2.md §2`

### 3.1 Strong Customer Authentication (SCA)

**Current state:** NOT compliant — email + password only (single factor). No BankID integration.
**Required:** BankID integration for SCA (Phase 2, BLOCKING for live transactions).

| SCA Requirement | Law | Status |
|----------------|-----|--------|
| Two of three factors (knowledge/possession/inherence) | Betalingstjenesteloven §§ 4-28, 4-29 | NOT IMPLEMENTED |
| Dynamic linking (amount + payee bound to auth code) | Delegated Reg. (EU) 2018/389 Art. 5 | NOT IMPLEMENTED |
| 90-day re-authentication | Delegated Reg. Art. 10 | NOT IMPLEMENTED |
| BankID integration (covers possession + knowledge) | Required for Norwegian residents | PLANNED Phase 2 |

### 3.2 Open Banking (AISP/PISP)

| Requirement | Status |
|------------|--------|
| AISP license or agent arrangement | NOT OBTAINED |
| PISP license or agent arrangement | NOT OBTAINED |
| PSD2 API integration (Neonomics) | PLANNED Phase 2 |
| No storing of bank credentials | Architectural (pass-through model) |
| PSU explicit consent before account access | PLANNED Phase 2 |

**Licensing path:** Agent model under licensed PSP (1-3 months) while preparing full license (6-12 months). See §4.

### 3.3 Consumer Protection (PSD2)

| Requirement | Status | Document |
|------------|--------|---------|
| Framework agreement | Draft | `legal/brukervilkar.md` |
| Fee transparency pre-authorization | Partial | Fee shown post-submission in API |
| Transaction receipts | Not built | Phase 2 |
| Execution time disclosure | Not built | Phase 2 |

---

## 4. Finanstilsynet Licensing

**Source:** `legal/drop-regulatory-map-v2.md §1`, `legal/konsesjonssoknad-forberedelse.md`

### 4.1 License Options

| Option | Timeline | Capital | Scope |
|--------|---------|---------|-------|
| Agent model (under existing licensee) | 1-3 months | None from Drop | Fastest to market |
| Begrenset betalingsforetak | 3-6 months | None (simplified) | Max 6M NOK/month volume |
| Ordinaert betalingsforetak | 6-12 months | 125,000 EUR | Full EEA passporting |

**Recommended path:** Agent model first → Begrenset betalingsforetak for initial launch → Ordinaert for Scandinavian expansion.

### 4.2 Licensing Readiness

| Requirement | Status | Gap |
|-------------|--------|-----|
| Business plan with 3-year projections | Draft | Partial |
| AML policy and procedures | Draft | `legal/hvitvaskingsrutiner.md` |
| Fit & proper documentation | Not done | Board/management CVs + police certs needed |
| Compliance officer designated | Not done | Appointment required |
| Client fund safeguarding | N/A (pass-through) | N/A — Drop never holds funds |
| IT security policy | Draft | `legal/ikt-sikkerhetspolicy.md` |
| Incident handling plan | Draft | `legal/hendelseshaandtering.md` |
| Outsourcing policy | Draft | `legal/utkontraktering-policy.md` |

---

## 5. AML/KYC Compliance

**Source:** `legal/hvitvaskingsrutiner.md`, `legal/risikovurdering-hvitvasking.md`

### 5.1 AML Program Status

| Requirement | Status | Document |
|------------|--------|---------|
| Enterprise-wide risk assessment | Draft | `legal/risikovurdering-hvitvasking.md` |
| AML policy and procedures | Draft | `legal/hvitvaskingsrutiner.md` |
| AML Compliance Officer appointed | NOT DONE | Appointment required |
| KYC procedures (CDD) | Mock only | Real KYC via BankID + Sumsub — Phase 2 |
| Transaction monitoring system | NOT IMPLEMENTED | Phase 2 |
| PEP screening | NOT IMPLEMENTED | Phase 2 (ComplyAdvantage / Refinitiv) |
| Sanctions screening | NOT IMPLEMENTED | Phase 2 |
| STR reporting to EFE (Altinn) | NOT IMPLEMENTED | Phase 2 |
| Staff AML training | NOT DONE | Required |
| 5-year record retention | NOT IMPLEMENTED | Phase 2 |

### 5.2 Transaction Monitoring Thresholds (Planned)

| Rule | Threshold | Action |
|------|---------|--------|
| Single transaction | > NOK 50,000 | Manual review |
| Daily cumulative | > NOK 100,000 | Manual review |
| Monthly cumulative | > NOK 500,000 | EDD assessment |
| High-risk corridor transactions | > 5/week same corridor | Manual review |
| Structuring detection | Multiple just-under-threshold | Automatic flag |

**Source:** `legal/hvitvaskingsrutiner.md §5.2`

### 5.3 Corridor Risk Classification

| Risk Level | Corridors | Actions |
|-----------|---------|---------|
| Low | EU/EEA (PLN, EUR), UK | Standard CDD |
| Medium | Serbia (RSD), Bosnia (BAM), Turkey (TRY) | Standard CDD + lower thresholds |
| High | Pakistan (PKR) | Mandatory EDD, source of funds required |
| Blocked | FATF blacklist / EU high-risk / UN sanctions | System-level block |

---

## 6. Data Classification Scheme

| Level | Label | Description | Examples | Controls |
|-------|-------|------------|---------|---------|
| L1 | Public | Public-facing content | Landing page, marketing | None |
| L2 | Internal | Internal, low sensitivity | Internal wikis, non-PII analytics | Access control |
| L3 | Confidential | Sensitive personal or business data | User PII (name, email, phone), transaction data | Encryption + access control + logging |
| L4 | Restricted | Highest sensitivity, regulatory implications | Fødselsnummer, AML reports, JWT secrets | Field-level encryption + MFA + strict access + audit + HSM keys |

---

## 7. Consent Management

### 7.1 Consent Types Required

| Consent Type | Purpose | Status |
|-------------|---------|--------|
| Open Banking (AISP) | Reading bank account balances | Planned Phase 2 — PSD2 explicit consent required |
| Marketing emails | Email campaigns | Not implemented |
| Analytics | Product improvement | Not implemented |
| Cookie consent | Website cookies | Not implemented |

### 7.2 PSD2 Open Banking Consent Requirements

Per Betalingstjenesteloven §§ 4-41 to 4-46:
- Explicit user consent before any AISP access to bank accounts
- Consent scoped per bank account
- Re-consent required every 90 days for AISP
- Consent revocable at any time (immediate effect)
- Consent stored with timestamp, IP, and scope in `user_consents` table (planned)

---

## 8. Audit Schedule & Methodology

| Audit Type | Frequency | Scope | Owner | Last Done | Status |
|-----------|-----------|-------|-------|----------|--------|
| Internal security review | Quarterly | Application + infrastructure | Security team | 2026-02-12 | Completed |
| Penetration test | Annual | Full scope | External firm (TBD) | Not done | Planned pre-launch |
| AML/compliance review | Annual | All AML procedures | AML Compliance Officer | Not done | Planned Phase 2 |
| GDPR compliance review | Annual | All processing activities | DPO | Not done | Planned Phase 2 |
| Vulnerability assessment | Quarterly | External attack surface | Security team | 2026-02-12 | Completed |
| Business continuity drill | Annual | DR/BCP scenarios | Operations | Not done | Planned Phase 2 |

---

## 9. Compliance Training Requirements

| Training | Audience | Frequency | Status |
|---------|---------|-----------|--------|
| AML fundamentals (hvvl.) | All staff | Annual + onboarding | Not done — required |
| GDPR fundamentals | All staff handling personal data | Annual | Not done |
| Secure coding (OWASP) | Engineering | Annual | Not done |
| Incident response tabletop | Engineering + Management | Quarterly | Not done |
| PEP/sanctions screening procedures | Compliance + customer-facing | Annual | Not done |

**Source:** `legal/hvitvaskingsrutiner.md §10`

---

## 10. Third-Party Compliance Requirements

### 10.1 Critical Vendor Register

| Vendor | Service | Tier | Certifications | DPA Signed | Status |
|--------|---------|------|---------------|----------|--------|
| BankID Norge AS | SCA / Identity | Critical | eIDAS Level High | Required | Planned |
| Sumsub | KYC/AML | Critical | SOC 2, ISO 27001 | Yes — `legal/dpa-sumsub.md` | Signed |
| Swan | Banking / payment rails | Critical | PCI-DSS, SOC 2 | Yes — `legal/dpa-swan.md` | Signed |
| Neonomics | PSD2 AISP/PISP | Critical | PSD2 license (EU) | Required | Planned |
| AWS | Infrastructure | Critical | SOC 2 Type II, ISO 27001, PCI-DSS | AWS DPA | Standard |
| Sentry | Error monitoring | High | SOC 2 | Yes — `legal/dpa-sentry.md` | Signed |

### 10.2 Outsourcing Policy

**Source:** `legal/utkontraktering-policy.md`

All material outsourcing relationships must:
- Have a written contract with DPA if processing personal data
- Include right to audit clause
- Include sub-processor approval requirements
- Have an exit strategy documented
- Be notified to Finanstilsynet if material (Finansforetaksloven § 13-7)

---

## 11. Compliance Monitoring

**Current state:** Manual tracking only. No automated compliance dashboard.

**Target metrics (Phase 2):**

| Metric | Target | Alert Threshold |
|--------|--------|----------------|
| Open Critical compliance issues | 0 | > 0 |
| KYC approval backlog | < 24h | > 48h |
| AML flagged transactions unreviewed | 0 after 24h | > 0 after 48h |
| Data subject requests overdue | 0 | > 25 days |
| License application milestones | On schedule | Any delay |
| Vendor certifications expired | 0 | > 0 |
| AML training completion | 100% | < 100% |

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | ALAI Compliance Team | 2026-02-23 | |
| DPO | TBD — appointment required | | |
| CISO | TBD — appointment required | | |
| Legal Counsel | TBD — engagement required | | |
| CEO | Alem Bašić | | |

# Data Protection Impact Assessment (DPIA)

# Data Protection Impact Assessment (DPIA)

> **Project:** Bilko — Balkan Accounting SaaS
> **Processing Activity:** Multi-Tenant Accounting Data Processing (invoices, expenses, VAT, tax filings)
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** Compliance Architect
> **DPO:** TBD (dpo@bilko.io)
> **Status:** Draft
> **Reviewers:** DPO, Legal Counsel, Engineering Lead
> **Classification:** Confidential

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1 | 2026-02-23 | Compliance Architect | Initial DPIA — accounting SaaS data processing |

---

## DPIA Trigger Checklist

| # | Trigger | Applies? | Notes |
|---|---------|---------|-------|
| 1 | Systematic profiling / automated decisions with legal effects | NO | No profiling or automated decisions |
| 2 | Large-scale special category data (health, religion, ethnicity) | NO | Financial data is not Art. 9 special category |
| 3 | Systematic monitoring of publicly accessible areas | NO | N/A |
| 4 | Biometric or genetic data | NO | N/A |
| 5 | Data about vulnerable subjects (children, patients) | PARTIAL | Employees submitting expense claims via employer's Bilko account |
| 6 | Innovative technology with unpredictable privacy impact | NO | Standard cloud accounting SaaS |
| 7 | Cross-border transfer outside EEA without adequate protection | YES | Serbian (ZZPL) and BiH (ZZLP) data subjects — data processed on EU servers |
| 8 | Processing that prevents data subjects from exercising rights | NO | Self-service endpoints provided |

**DPIA Required:** YES
**Reason:** Cross-border transfer of RS/BA data subjects to EU-hosted infrastructure; processing of national tax IDs (PIB/JMBG/OIB/JIB) as sensitive identifiers; financial data for thousands of organizations and their counterparties.

---

## 1. Processing Activity Description

### 1.1 Activity Overview

**Activity Name:** Multi-Tenant Accounting Data Processing
**System/Product:** Bilko (bilko.io)
**Business Unit:** Product
**Processing Owner:** Engineering Lead (engineering@bilko.io)

**Description of Processing:**
Bilko processes financial and personal data on behalf of business organizations (data controllers) in Serbia, Bosnia & Herzegovina, and Croatia:
1. User account data — email, full name, bcrypt-hashed password, TOTP secret
2. Organization data — legal name, tax ID (PIB/JIB/OIB), address, banking details
3. Contact data — customer/supplier names, tax IDs, email, phone, IBAN
4. Invoice data — invoice numbers, dates, amounts, VAT, payment status
5. Expense data — amounts, categories, descriptions, receipt attachments
6. Transaction data — bank transactions, double-entry debit/credit
7. Audit trail — all mutations logged with user ID, IP, timestamp, old/new values

**Business Justification:**
SMBs in the Balkans lack affordable, compliant cloud accounting software. Processing this data is necessary to deliver the contracted accounting service and meet legal obligations under RS/BA/HR accounting and tax laws.

### 1.2 Processing Operations

| Operation | Data Category | Technology | Location |
|-----------|--------------|-----------|---------|
| Collection (registration) | Email, name, password | HTTPS POST, Zod validation | bilko.io — Vercel EU |
| Collection (invoicing) | Contact data, tax IDs, amounts | HTTPS API | api.bilko.io — Railway EU West |
| Storage | All categories | PostgreSQL AES-256 | Railway EU West (Frankfurt/Paris) |
| Storage (files) | Receipt PDFs/JPGs | Cloudflare R2 AES-256 | Cloudflare EU region |
| Processing | VAT calculations, reports | Express API, Prisma ORM | Railway EU West |
| Sharing | Invoice PDFs | SendGrid email | EU region |
| E-invoice submission | Invoice XML | UBL 2.1 SEF / HR-FISK | Serbia (SEF) / Croatia (FINA) |
| Deletion / Anonymization | User PII | Soft delete + anonymization | Railway EU West |

---

## 2. Necessity & Proportionality Assessment

### 2.1 Purposes of Processing

| Purpose | Specific Description | Legitimate? |
|---------|--------------------|-----------|
| Account authentication | Verify user identity for organization access | YES — contract necessity (Art. 6.1.b) |
| Invoice generation | Create legally compliant invoices with tax IDs required by law | YES — legal obligation (Art. 6.1.c) |
| VAT calculation and reporting | Calculate VAT at RS 20% / BA 17% / HR 25% | YES — legal obligation (Art. 6.1.c) |
| Financial record keeping | Double-entry books required by accounting law | YES — legal obligation (Art. 6.1.c) |
| Audit trail | Immutable log of financial mutations | YES — legal obligation + legitimate interest (Art. 6.1.c + 6.1.f) |
| E-invoice submission | Submit to SEF (Serbia) and HR-FISK (Croatia) | YES — legal obligation (Art. 6.1.c) |
| Invoice email delivery | Deliver invoices to customers | YES — contract necessity (Art. 6.1.b) |

### 2.2 Data Minimization Assessment

| Data Element | Collected | Strictly Necessary? | Alternative |
|-------------|-----------|--------------------|-----------------------------|
| `email` | YES | YES — login + invoice delivery | N/A |
| `fullName` | YES | YES — required on invoices | N/A |
| `passwordHash` | YES | YES — authentication | N/A (bcrypt hash) |
| `totpSecret` | YES | YES — 2FA | N/A |
| `organizationTaxId` (PIB/JIB/OIB) | YES | YES — legally required on invoices | N/A |
| `organizationIban` | YES | YES — payment reference | N/A |
| `contactTaxId` (PIB/JMBG/OIB/JIB) | YES | YES — VAT law requires buyer tax ID | N/A |
| `contactIban` | YES | PARTIAL — needed only for payment features | Collect only when payment active |
| `clientIp` (audit log) | YES | YES — security, fraud detection | Retain max 30 days for security entries |
| `userAgent` | NO | NO — not collected | Not collected |
| `deviceId` | NO | NO — not collected | Not collected |

### 2.3 Lawful Basis

| Processing Activity | Lawful Basis | Justification |
|--------------------|-----------|----|
| Account management | Contract — Art. 6.1.b | Necessary to provide the service |
| Invoice / expense / transaction processing | Legal obligation — Art. 6.1.c | Zakon o PDV + Zakon o računovodstvu |
| VAT calculation and reporting | Legal obligation — Art. 6.1.c | Mandatory under RS/BA/HR tax laws |
| Audit trail | Legal obligation + Legitimate interest — Art. 6.1.c + 6.1.f | Accounting law + fraud detection |
| IP address logging | Legitimate interest — Art. 6.1.f | Security monitoring, 30-day retention |
| E-invoice submission (SEF/HR-FISK) | Legal obligation — Art. 6.1.c | Mandatory electronic submission |
| Data retention beyond erasure | Legal obligation — Art. 6.1.c | 10-11 year retention per accounting law |

---

## 3. Data Subjects & Categories of Data

### 3.1 Data Subject Groups

| Group | Description | Estimated Volume | Vulnerability |
|-------|------------|-----------------|--------------|
| Business owners / admins | SMB owners using Bilko | 1-10 per organization | Low |
| Accountants | Professional accountants on client accounts | 1-5 per organization | Low |
| Employees | Submitting expense claims via employer's account | Variable | Low-Medium |
| Counterparties | Customers/suppliers appearing on invoices | External to Bilko | Low (financial risk if breached) |

### 3.2 Personal Data Categories

| Category | Data Elements | Sensitivity | Classification |
|---------|--------------|------------|---------------|
| Contact | Name, email, phone | Standard | L3 Confidential |
| Authentication | bcrypt(password), TOTP secret | High | L4 Restricted |
| Tax identity | PIB, JMBG, OIB, JIB | High | L4 Restricted |
| Financial | Invoice amounts, IBAN, bank transactions | High | L3 Confidential |
| Behavioral / Audit | IP address, action timestamps, old/new values | Standard | L2 Internal |
| Attachments | Receipt PDFs, invoice attachments | Standard-High | L3 Confidential |

---

## 4. Data Processing Purposes & Legal Basis

| Processing Purpose | Personal Data Used | Legal Basis | Retention Period |
|-------------------|--------------------|------------|-----------------|
| User authentication | email, passwordHash, totpSecret | Contract (Art. 6.1.b) | Until account deletion |
| Invoice creation and delivery | org name, tax ID, contact name/tax ID/address, amounts | Legal obligation (Art. 6.1.c) | 10 years (RS) / 11 years (HR/BA RS entity) |
| VAT calculation | Invoice amounts, tax rates, tax IDs | Legal obligation (Art. 6.1.c) | 10-11 years |
| Expense management | Amounts, descriptions, receipt images | Legal obligation (Art. 6.1.c) | 10-11 years |
| Financial reporting | All financial data | Legal obligation (Art. 6.1.c) | 10-11 years |
| Audit trail | userId, IP, timestamp, changedFields | Legal obligation + Legitimate interest | Financial: 10-11 years; IP/security: 30 days |
| E-invoice (SEF/HR-FISK) | Invoice XML with buyer/seller data | Legal obligation (Art. 6.1.c) | Per SEF/FINA requirements |
| Transactional email | email, invoice PDF | Contract (Art. 6.1.b) | Not stored by Bilko |
| Data export (portability) | All data | GDPR Art. 20 / ZZPL Art. 37 | On demand |

---

## 5. Data Flow Mapping

```mermaid
flowchart TD
    DS([Data Subject\nBusiness user]) -->|Registers / Creates invoice| COLLECT[Web App\nbilko.io — Vercel EU]
    COLLECT -->|HTTPS POST Zod validated| API[Express API\napi.bilko.io — Railway EU West]
    API -->|Prisma ORM AES-256| DB[(PostgreSQL\nRailway EU West\nFrankfurt/Paris)]
    API -->|Receipt upload| R2[(Cloudflare R2\nEU region AES-256)]
    DB -->|Append-only| AUDIT[(LoggedAction\nImmutable audit trail)]
    API -->|Invoice email| SG[SendGrid\nEU region]
    API -->|UBL 2.1 XML| SEF[SEF efaktura.gov.rs\nSerbia]
    API -->|UBL 2.1 HR-CIUS| FINA[HR-FISK fina.hr\nCroatia]
    DB -->|On deletion| ANON[Anonymization\nPII removed]
    DB -->|On export| EXPORT[JSON export to data subject]
    ANON --> DB

    style DB fill:#ffcccc,stroke:#cc0000
    style AUDIT fill:#ffe4cc,stroke:#cc6600
```

**Data processors (GDPR Art. 28):**
| Processor | Service | Data Shared | Country | DPA Status |
|---------|---------|------------|---------|----------|
| Railway | PostgreSQL hosting | All accounting data | EU West | Pending — sign before launch |
| Vercel | Frontend hosting | Browser requests | Global / EU edge | Pending |
| Cloudflare | CDN, WAF, R2 | IP addresses, receipt files | EU region | Pending |
| SendGrid | Transactional email | Email, invoice PDFs | EU | Pending |

---

## 6. Risk Assessment Matrix

### 6.1 Risk Scoring

Risk Score = Likelihood (1-5) × Severity (1-5). Score 1-6: Low; 7-12: Medium; 13-19: High; 20-25: Critical.

### 6.2 Risks to Data Subjects

| Risk ID | Risk | Likelihood | Severity | Score | Controls | Residual |
|---------|------|-----------|---------|-------|---------|---------|
| R1 | Unauthorized access to financial data (tax IDs, IBAN, invoice amounts) | 2 | 5 | 10 | TLS 1.3, AES-256, RBAC, org-scoped queries | MEDIUM |
| R2 | Tax ID (PIB/JMBG/OIB/JIB) theft enabling identity fraud | 2 | 5 | 10 | L4 Restricted, RBAC, no JWT exposure, bcrypt | MEDIUM |
| R3 | Cross-tenant data access (IDOR) | 2 | 5 | 10 | org-scoped WHERE on all queries, UUID PKs | LOW (after mitigation) |
| R4 | Invoice data exposure to wrong party | 2 | 4 | 8 | RBAC, org-scope, input validation | MEDIUM |
| R5 | Financial data manipulation (tampered invoices) | 2 | 5 | 10 | Immutable LoggedAction, RBAC delete permissions | LOW (after mitigation) |
| R6 | PII retained beyond necessary period | 2 | 3 | 6 | Soft delete + anonymization; financial data by law | LOW |
| R7 | Cross-border transfer (RS/BA data to EU) without protection | 3 | 3 | 9 | Railway EU West; ZZPL Art. 65; data stays in EEA | MEDIUM |
| R8 | User unable to exercise erasure (locked by retention law) | 3 | 2 | 6 | Clear policy in privacy notice; PII anonymized | LOW |
| R9 | Receipt with sensitive data (medical receipts) exposed | 2 | 3 | 6 | Encrypted R2 storage; RBAC accountant+ access | LOW |
| R10 | Audit log IP retention enabling tracking | 2 | 2 | 4 | 30-day IP retention for security; longer for financial | LOW |
| R11 | SEF/HR-FISK state submission | 1 | 2 | 2 | Legal obligation — mandated by law | ACCEPTABLE |

---

## 7. Mitigation Measures

| Risk ID | Mitigation | Owner | Deadline | Status |
|---------|-----------|-------|---------|--------|
| R1 | Field-level encryption for tax IDs and IBAN at application layer | Engineering | Phase 2 | Planned |
| R1 | Sentry error tracking + Railway anomaly monitoring | Platform | Phase 1 | Planned |
| R2 | Tax IDs L4 Restricted — never in JWT, never in logs | Engineering | Phase 1 | Designed |
| R3 | Integration tests: cross-tenant requests must return 403 | Engineering | Phase 1 | Planned |
| R4 | Vitest RBAC tests on all protected endpoints | Engineering | Phase 1 | Planned |
| R5 | LoggedAction PostgreSQL trigger — append-only, no DELETE | Engineering | Phase 1 | Designed |
| R7 | Privacy notice states Railway EU West data residency; ZZPL Art. 65 transfer basis | Legal/DPO | Phase 1 | Planned |
| R7 | Verify Railway region = EU West before launch | Platform | Phase 1 | Planned |
| R8 | Privacy notice explains legal retention basis clearly | Legal/DPO | Phase 1 | Planned |
| R9 | R2 files accessible only to org members with accountant+ role | Engineering | Phase 1 | Designed |

**Residual risk conclusion:** All residual risks are LOW or MEDIUM. No CRITICAL or HIGH residual risks. Processing may proceed subject to DPO approval and Phase 1 mitigations being implemented before first paying customer.

**DPO Conclusion:** ☐ Acceptable — proceed | ☐ Conditional — pending Phase 1 mitigations | ☐ Escalate to supervisory authority

---

## 8. DPO Consultation Record

**DPO Name:** TBD (dpo@bilko.io)
**Consultation Date:** To be scheduled before first paying customer

**DPO Input:**
> [To be completed after DPO appointment]

**DPO Recommendation:**
- [ ] Approved — risks acceptable and adequately mitigated
- [ ] Conditional approval — subject to Phase 1 mitigations
- [ ] Rejected — redesign required
- [ ] Escalate to supervisory authority

**DPO Signature:** _________________________ Date: _____________

---

## 9. Supervisory Authority Consultation

**Consultation Required:** NO (pending DPO review)
**Reason:** No CRITICAL or HIGH residual risks. MEDIUM risks standard for cloud accounting SaaS.

**If required — relevant authorities by jurisdiction:**
- HR: AZOP — azop@azop.hr — https://azop.hr
- RS: Poverenik — office@poverenik.rs — https://www.poverenik.rs
- BA: AZLP — info@azlp.ba — https://www.azlp.ba

---

## 10. DPIA Review Schedule

**Next review:** 2027-02-23 (annual) or when:
- New country launch (RS Phase 2, HR Phase 2, BA Phase 3)
- New government integration (SEF, HR-FISK, CPF)
- Data breach or near-miss
- New data categories or processors
- Regulatory changes (ZZPL amendment, BiH reform, GDPR updates)

**Review Owner:** DPO (dpo@bilko.io)
**Review Log:**
| Date | Reviewer | Changes | Outcome |
|------|---------|---------|---------|
| 2026-02-23 | Compliance Architect | Initial DPIA | Draft — awaiting DPO |

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | Compliance Architect | 2026-02-23 | |
| DPO | | | |
| Engineering Lead | | | |
| Legal Counsel | | | |
| CEO | | | |

# Data Encryption Policy

# Data Encryption Policy

> **Project / Organization:** Bilko — Balkan Accounting SaaS
> **Policy Number:** POL-SEC-ENC-001
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** Compliance Architect
> **Status:** Draft
> **Reviewers:** CTO, DPO, Engineering Lead
> **Next Review:** 2026-08-23
> **Classification:** Confidential

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1 | 2026-02-23 | Compliance Architect | Initial draft — Bilko encryption standards |

---

## 1. Purpose & Scope

**Purpose:** This policy defines minimum encryption standards for all data at rest, in transit, and at the application level across all systems operated by Bilko.

**Scope:**
- All systems operated by Bilko (api.bilko.io, bilko.io, PostgreSQL, Cloudflare R2)
- All employees, contractors, and third parties with access to Bilko systems
- All data classified Internal, Confidential, or Restricted (see compliance-framework.md §6)
- Railway PostgreSQL (EU West), Vercel (frontend), Cloudflare R2 (file storage)

**Regulatory basis:**
- GDPR Art. 32 — Appropriate technical measures including encryption
- ZZPL Art. 50 (Serbia) — Security of personal data processing
- ZZLP Art. 14 (BiH) — Technical data protection measures
- Zakon o računovodstvu (RS/HR/BA) — Integrity of financial records

---

## 2. Encryption Standards & Approved Algorithms

### 2.1 Approved Algorithms

#### Symmetric Encryption

| Use Case | Algorithm | Key Size | Mode | Notes |
|---------|-----------|---------|------|-------|
| Data at rest (general) | AES | 256-bit | GCM | Authenticated encryption — default |
| Database disk encryption | AES | 256-bit | XTS | Railway PostgreSQL default |
| File storage | AES | 256-bit | GCM | Cloudflare R2 server-side |
| Backup encryption | AES | 256-bit | GCM | Railway automatic backup encryption |

#### Asymmetric Encryption

| Use Case | Algorithm | Key Size | Notes |
|---------|-----------|---------|-------|
| TLS key exchange | ECDHE | P-256 / P-384 | Cloudflare + Railway TLS 1.3 |
| JWT signing | HMAC-SHA-256 (HS256) | 256-bit | JWT_SECRET (32+ chars, CSPRNG) |
| JWT refresh | HMAC-SHA-256 (HS256) | 256-bit | JWT_REFRESH_SECRET (separate key) |
| Future: JWT asymmetric | Ed25519 (EdDSA) | 256-bit | Planned Phase 2 migration |

#### Hashing & Password Storage

| Use Case | Algorithm | Parameters | Notes |
|---------|-----------|-----------|-------|
| Password hashing | bcrypt | cost factor = 12 | `bcrypt.hash(password, 12)` |
| Token hashing (refresh tokens) | HMAC-SHA-256 | — | Refresh tokens hashed before DB storage |
| Data integrity (general) | SHA-256 | — | File checksums, non-security hashing |

### 2.2 Prohibited Algorithms (NEVER USE)

| Algorithm | Reason |
|-----------|--------|
| MD5 | Collision attacks (2004+) — completely broken |
| SHA-1 | Collision attacks (2017+) |
| DES / 3DES | Key size insufficient |
| RC4 | Statistical biases |
| ECB mode (any cipher) | Leaks data patterns — deterministic |
| RSA < 2048-bit | Insufficient key strength |
| AES-128 for Restricted data | Insufficient for financial/tax ID data |
| bcrypt < 12 rounds | Insufficient work factor |
| MD5 for password hashing | NEVER — use bcrypt |

---

## 3. Encryption at Rest

### 3.1 Database Encryption

| Database | Method | Key Management | Coverage |
|---------|--------|---------------|---------|
| PostgreSQL (Railway EU West) | AES-256 disk encryption (Railway TDE) | Railway-managed | All data classifications |
| PostgreSQL — tax IDs (PIB/JMBG/OIB/JIB) | AES-256-GCM application-layer field encryption | Environment variable (Railway secrets) | L4 Restricted |
| PostgreSQL — IBAN | AES-256-GCM application-layer field encryption | Environment variable (Railway secrets) | L4 Restricted |
| PostgreSQL backups | AES-256 (Railway automatic) | Railway-managed | All data |

**Field-level encryption for L4 Restricted data:**
```typescript
// Tax IDs and bank account numbers encrypted at application layer
// In addition to Railway disk encryption
import crypto from 'crypto';

async function encryptRestrictedField(plaintext: string): Promise<string> {
  const key = Buffer.from(process.env.FIELD_ENCRYPTION_KEY!, 'hex'); // 32 bytes
  const iv = crypto.randomBytes(12); // 96-bit IV for GCM
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  // Store: base64(iv || tag || ciphertext)
  return Buffer.concat([iv, tag, ciphertext]).toString('base64');
}

async function decryptRestrictedField(stored: string): Promise<string> {
  const key = Buffer.from(process.env.FIELD_ENCRYPTION_KEY!, 'hex');
  const buf = Buffer.from(stored, 'base64');
  const iv = buf.subarray(0, 12);
  const tag = buf.subarray(12, 28);
  const ciphertext = buf.subarray(28);
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
  decipher.setAuthTag(tag);
  return decipher.update(ciphertext) + decipher.final('utf8');
}
```

### 3.2 File Storage Encryption

| Storage | Method | Key Management | Notes |
|---------|--------|---------------|-------|
| Cloudflare R2 (receipts, invoice PDFs) | AES-256 server-side (Cloudflare default) | Cloudflare-managed | EU region bucket required |

### 3.3 Backup Encryption

Railway provides automatic daily PostgreSQL backups, encrypted with AES-256. Backup retention: 30 days (Railway default). Custom backup strategy to be implemented in Phase 2 with separate backup encryption key stored in Railway environment secrets.

---

## 4. Encryption in Transit

### 4.1 TLS Configuration

**Minimum TLS version:**
- External-facing (api.bilko.io, bilko.io): TLS 1.3 (enforced via Cloudflare)
- Railway internal: TLS 1.3

**Approved cipher suites (TLS 1.3):**
```
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256 (minimum)
```

**HSTS configuration:**
```
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```

**HTTP redirect:** All HTTP traffic redirected to HTTPS (301). No HTTP allowed in production.

**Prohibited:**
- TLS 1.0 and TLS 1.1 — prohibited
- SSL 2.0 / SSL 3.0 — prohibited
- RC4 cipher suites — prohibited

### 4.2 Cookie Security

All session cookies set with:
```typescript
res.cookie('refreshToken', token, {
  httpOnly: true,    // Not accessible to JavaScript
  secure: true,      // HTTPS only
  sameSite: 'strict', // CSRF protection
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
```

### 4.3 API Security Headers (Helmet.js)

```typescript
app.use(helmet({
  hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
    },
  },
}));
```

---

## 5. Application-Level Encryption

### 5.1 Field-Level Encryption Requirements

**Required for all L4 Restricted fields:**
| Field | Table | Reason |
|-------|-------|--------|
| `taxId` (organization tax ID: PIB/JIB/OIB) | `organizations` | National business identifier |
| `taxId` (contact tax ID: PIB/JMBG/OIB/JIB) | `contacts` | National person/business identifier |
| `iban` | `organizations`, `contacts`, `bankAccounts` | Bank account number |
| `totpSecret` | `users` | 2FA seed — must not be readable |

### 5.2 JWT Token Encryption

JWTs signed with HMAC-SHA-256 (HS256) using `JWT_SECRET`:
- `JWT_SECRET`: 32+ character random string (CSPRNG), stored in Railway secrets
- `JWT_REFRESH_SECRET`: Separate 32+ character key — never same as JWT_SECRET
- Access token payload: `{ sub: userId, org: organizationId, role, iat, exp }`
- NO PII in JWT payload — no email, name, tax ID

Refresh tokens stored as HMAC-SHA-256 hash in database — raw token never stored.

### 5.3 Password Hashing

```typescript
import bcrypt from 'bcrypt';

// Registration
const hash = await bcrypt.hash(plainPassword, 12);

// Login verification
const isValid = await bcrypt.compare(plainPassword, storedHash);
```

bcrypt parameters: cost factor 12. Never downgrade below 12.

---

## 6. Key Management Summary

**Full policy:** [key-management-policy.md](./key-management-policy.md)

| Key Type | Storage | Rotation | Owner |
|---------|---------|---------|-------|
| `JWT_SECRET` | Railway environment secret | Quarterly | Security |
| `JWT_REFRESH_SECRET` | Railway environment secret | Quarterly | Security |
| `FIELD_ENCRYPTION_KEY` (tax IDs, IBAN) | Railway environment secret | Annual | Security |
| PostgreSQL disk encryption | Railway-managed (TDE) | Railway-managed | Railway |
| Cloudflare R2 encryption | Cloudflare-managed | Cloudflare-managed | Cloudflare |
| TLS certificates (Cloudflare) | Cloudflare Certificate Manager | 90 days (automatic) | Cloudflare |

**Secrets never committed to git.** `.env` files in `.gitignore`. All secrets managed via Railway environment variables (production) or `.env.local` (development, git-ignored).

---

## 7. Cryptographic Inventory

| System | Algorithm | Key Size | Mode | Key Location | Next Rotation |
|--------|-----------|---------|------|-------------|--------------|
| PostgreSQL disk (Railway) | AES-256 | 256-bit | XTS | Railway-managed | Railway-managed |
| Tax ID field encryption | AES-256-GCM | 256-bit | GCM | Railway env secret | Annual |
| IBAN field encryption | AES-256-GCM | 256-bit | GCM | Railway env secret | Annual |
| Cloudflare R2 | AES-256 | 256-bit | — | Cloudflare-managed | Cloudflare-managed |
| External TLS (Cloudflare) | ECDSA P-256 | 256-bit | — | Cloudflare | 90 days (auto) |
| JWT signing | HMAC-SHA-256 | 256-bit | — | Railway env secret | Quarterly |
| Refresh token signing | HMAC-SHA-256 | 256-bit | — | Railway env secret | Quarterly |
| Password hashing | bcrypt | cost=12 | — | N/A | N/A |

---

## 8. Financial Data Integrity

Financial data requires not just confidentiality but also integrity. Bilko enforces:

1. **NUMERIC(19,4) for all monetary amounts** — never float or JavaScript number. Prevents rounding errors in VAT calculations.
2. **Immutable LoggedAction table** — all mutations append-only with old/new values. Enables financial audit.
3. **Double-entry enforcement** — debit = credit validated at backend. Prevents imbalanced entries.
4. **Exchange rate locking** — rates stored at transaction date. Historical accuracy preserved.

---

## 9. Exception Process

**Exceptions not permitted for:** L4 Restricted data (tax IDs, IBAN, TOTP secrets, password hashes) — no exceptions.

**Exception request process:**
1. Submit to: security@bilko.io
2. Required: system affected, algorithm excepted from, business justification, risk assessment, compensating controls, proposed duration (max 12 months)
3. Approval: CTO
4. Log: `/docs/security/exceptions.md`
5. Review: Quarterly

**Active exceptions:** None at this time.

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | Compliance Architect | 2026-02-23 | |
| CTO | | | |
| DPO | | | |
| Engineering Lead | | | |

# Data Breach Response Plan

# Data Breach Response Plan

> **Organization:** Bilko — Balkan Accounting SaaS
> **Document Number:** IRP-BILKO-001
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** Compliance Architect
> **Status:** Draft
> **Reviewers:** DPO, Legal Counsel, Engineering Lead, CEO
> **Next Review:** 2026-08-23
> **Classification:** Confidential — Restricted Distribution

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1 | 2026-02-23 | Compliance Architect | Initial draft — three-jurisdiction notification requirements RS/BA/HR |

---

## IMPORTANT — Keep This Document Accessible

> This plan must be accessible even when systems are down. Maintain offline copies and ensure key contacts are saved in personal phones.
> Key offline contacts: compliance@bilko.io | security@bilko.io | dpo@bilko.io

---

## 1. Incident Classification

| Severity | Definition | Response Team | Max Time to Classify |
|---------|-----------|--------------|---------------------|
| **P1 — Critical** | Confirmed breach of Restricted/Confidential data (tax IDs, IBAN, financial records) affecting any number of data subjects; OR active system compromise | Full IRT + Management + Legal | 1 hour |
| **P2 — High** | Suspected breach with evidence; or confirmed breach of Internal data; or targeted attack detected | Core IRT + Security Lead | 2 hours |
| **P3 — Medium** | Security event with potential for breach; precautionary action required | Security team | 8 hours |
| **P4 — Low** | Security anomaly, no confirmed breach | Security team | Next business day |

**72-hour notification trigger:** P1 and P2 MUST be assessed for regulatory notification obligation across all active jurisdictions (HR: AZOP; RS: Poverenik; BA: AZLP).

**Financial data sensitivity note:** Bilko handles invoices with national tax IDs (PIB/JMBG/OIB/JIB) and IBAN numbers. Any breach exposing these fields is automatically P1 — tax identity theft has severe consequences for data subjects.

---

## 2. Detection Mechanisms

| Detection Method | Tool | Responsible | Alert Channel |
|----------------|------|------------|--------------|
| Error rate spike | Sentry | Platform team | Slack #alerts |
| Railway API/CPU anomalies | Railway metrics | Platform team | Slack #alerts |
| Failed authentication spike (>10 in 1h) | Auth service logs | Platform team | Slack #alerts |
| Cloudflare WAF block spike | Cloudflare dashboard | Platform team | Email |
| Audit log anomalies (unusual DELETE patterns) | LoggedAction monitoring | Security team | Slack #alerts |
| Third-party notification | Vendor contacts us | Security email | security@bilko.io |
| User-reported | Support ticket | Support team | Escalate to security |
| Penetration test finding | External pen tester | Security team | Direct report |

**24/7 security contact:** security@bilko.io | compliance@bilko.io

---

## 3. Response Team Roles & Contacts

| Role | Primary | Backup | Email |
|------|---------|--------|-------|
| Incident Commander | CEO (Alem) | Engineering Lead | alem@alai.no |
| Security Lead | Compliance Architect | Engineering Lead | security@bilko.io |
| Engineering Lead | Lead Developer | — | engineering@bilko.io |
| DPO (GDPR/ZZPL/ZZLP) | TBD | Compliance Architect | dpo@bilko.io |
| Legal Counsel | External (TBD) | — | legal@bilko.io |
| Communications | CEO | — | alem@alai.no |

**External regulatory contacts (72-hour notification recipients):**

| Jurisdiction | Authority | Contact | When |
|-------------|-----------|---------|------|
| **Croatia (HR)** | AZOP — Agencija za zaštitu osobnih podataka | azop@azop.hr / https://azop.hr/prijavapovrede | P1/P2 within 72h (GDPR Art. 33) |
| **Serbia (RS)** | Poverenik za informacije od javnog značaja i zaštitu podataka o ličnosti | office@poverenik.rs / https://www.poverenik.rs | P1/P2 within 72h (ZZPL Art. 56) |
| **Bosnia & Herzegovina (BA)** | AZLP — Agencija za zaštitu ličnih podataka BiH | info@azlp.ba / https://www.azlp.ba | P1/P2 within 72h (best practice) |

**Notify ALL jurisdictions where affected data subjects reside.** If a breach affects data from multiple countries, notify all relevant authorities simultaneously.

---

## 4. Response Procedures by Phase

### Phase 1: Detection & Identification (0–1 hour)

**Goal:** Confirm whether a breach occurred and scope it.

```
Step 1.1 — ALERT RECEIVED
  □ Log initial alert time: ________________
  □ Create incident channel: #incident-{DATE}-{N}
  □ Assign to security lead
  □ Start incident log (chronological — everything goes in the log)

Step 1.2 — INITIAL ASSESSMENT (within 30 minutes)
  □ What triggered the alert?
  □ Is this a false positive? (if YES → close P4, document)
  □ What systems are affected? (api.bilko.io, Railway DB, Cloudflare R2, SEF/HR-FISK connections)
  □ Is the attack/leak ongoing?
  □ What data categories potentially affected? (tax IDs, IBAN, invoice data, user credentials)
  □ Which jurisdictions are affected? (RS, BA, HR, or multiple)

Step 1.3 — CLASSIFY INCIDENT
  □ Assign severity: P1 / P2 / P3 / P4
  □ Notify Incident Commander (CEO)
  □ If P1/P2: Activate full IRT immediately
  □ If P3/P4: Notify Security Lead

Step 1.4 — EVIDENCE PRESERVATION (CRITICAL — before containment if safe)
  □ Export Railway logs (30-day window around incident)
  □ Export Cloudflare logs
  □ Export Sentry error timeline
  □ Query LoggedAction: SELECT * FROM logged_actions WHERE action_timestamp > [incident_start]
  □ Screenshot anomalous metrics
  □ DO NOT wipe or restart affected systems until forensics complete
```

**Exit criteria:** Incident classified, IRT assembled, evidence preserved, 72h clock started if P1/P2.

---

### Phase 2: Containment (1–4 hours)

**Goal:** Stop the breach. Prevent further data exposure.

```
Step 2.1 — IMMEDIATE CONTAINMENT
  □ Revoke all JWT refresh tokens (DELETE FROM refresh_tokens — forces re-login for all users)
  □ Block malicious IP at Cloudflare WAF
  □ Disable compromised API endpoint if applicable
  □ Rotate JWT_SECRET and JWT_REFRESH_SECRET (all active sessions invalidated)
  □ Rotate FIELD_ENCRYPTION_KEY if database field encryption compromised
  □ Isolate affected Railway services if active exfiltration

Step 2.2 — SCOPE ASSESSMENT
  □ Which data was accessed/exfiltrated? (query LoggedAction + Railway logs)
  □ Which data subjects affected? (query: SELECT COUNT(*) and list affected organizationIds)
  □ What jurisdictions affected? (RS data subjects? BA? HR?)
  □ What time period? (from: ___ to: ___)
  □ Were tax IDs (PIB/JMBG/OIB/JIB) or IBAN exposed?
  □ Were authentication credentials (password hashes) exposed?
  □ Draft scope statement for DPO and legal review

Step 2.3 — ASSESS CONTAINMENT IMPACT
  □ What services are disrupted by containment actions?
  □ Inform support team of expected user disruption
  □ Prepare user communication if service unavailability expected
```

**Exit criteria:** Active threat contained, no ongoing exfiltration, scope defined, 72h countdown tracked.

---

### Phase 3: Eradication (4–24 hours)

**Goal:** Remove root cause. Ensure attacker completely out.

```
Step 3.1 — ROOT CAUSE ANALYSIS
  □ How did the attacker gain access? (SQL injection? JWT bypass? Compromised credentials? Cloudflare misconfiguration?)
  □ Was there a vulnerability in Prisma query construction?
  □ Was org-scoping WHERE clause bypassed?
  □ How long was access maintained?
  □ Were other organizations' data accessible?

Step 3.2 — REMEDIATE ROOT CAUSE
  □ Apply security patch
  □ Fix vulnerability (e.g., missing org-scope, Zod bypass, rate limit bypass)
  □ Remove any unauthorized database entries or backdoors
  □ Rotate all secrets and API keys

Step 3.3 — VERIFICATION
  □ Run Playwright security test suite
  □ Verify org-scoping isolation tests pass
  □ Verify RBAC tests pass
  □ Confirm no persistence mechanisms
```

---

### Phase 4: Recovery (24–72 hours)

**Goal:** Restore systems safely.

```
Step 4.1 — SAFE RESTORATION
  □ Deploy patched version
  □ Rotate ALL credentials on affected systems
  □ Enable enhanced monitoring for 30 days post-recovery
  □ Verify data integrity (compare LoggedAction against expected state)
  □ Re-enable services in stages

Step 4.2 — STAKEHOLDER UPDATES
  □ Update incident channel with progress
  □ Brief CEO
  □ Prepare regulatory notifications (see §5)
  □ Prepare customer communication if applicable
```

---

### Phase 5: Post-Incident (72 hours – ongoing)

```
Step 5.1 — POST-MORTEM (within 5 business days)
  □ Blameless post-mortem meeting
  □ Complete timeline of events
  □ Root cause analysis (5 Whys)
  □ What went well / what failed
  □ Action items with owners and deadlines

Step 5.2 — REGULATORY COMPLIANCE
  □ All regulatory notifications filed (see §5)
  □ Data subject notifications sent if required
  □ DPA customer notifications
  □ Insurance claim filed

Step 5.3 — REMEDIATION TRACKING
  □ All action items in issue tracker
  □ Weekly review for 4 weeks
  □ Update DPIA with lessons learned
  □ Update this response plan
```

---

## 5. Notification Requirements

### 5.1 Croatia — AZOP (GDPR Art. 33 — 72 hours)

**Legal basis:** GDPR Regulation (EU) 2016/679, Article 33
**Required if:** Breach likely to result in a risk to rights and freedoms of natural persons
**Deadline:** Within **72 hours** of becoming aware (not confirmed — aware)
**Partial notification allowed:** Submit what's known within 72h, supplement later

**Authority:** AZOP — Agencija za zaštitu osobnih podataka
**Portal:** https://azop.hr/prijavapovrede
**Email:** azop@azop.hr
**DPO submits:** dpo@bilko.io

**Required information:**
- Nature of breach (categories, number of data subjects, number of records)
- DPO contact details
- Likely consequences
- Measures taken or proposed

### 5.2 Serbia — Poverenik (ZZPL Art. 56 — 72 hours)

**Legal basis:** Zakon o zaštiti podataka o ličnosti, Article 56
**Required if:** Breach likely to result in risk to rights and freedoms of individuals
**Deadline:** Within **72 hours** of becoming aware

**Authority:** Poverenik za informacije od javnog značaja i zaštitu podataka o ličnosti
**Portal:** https://www.poverenik.rs
**Email:** office@poverenik.rs
**Address:** Bulevar kralja Aleksandra 15, 11000 Belgrade, Serbia

### 5.3 Bosnia & Herzegovina — AZLP (72 hours — best practice)

**Legal basis:** Zakon o zaštiti ličnih podataka BiH — breach notification not explicitly mandated in same detail as GDPR, but best practice is 72-hour notification following GDPR standard.
**Deadline:** 72 hours (voluntary/best practice)

**Authority:** AZLP — Agencija za zaštitu ličnih podataka Bosne i Hercegovine
**Email:** info@azlp.ba
**Address:** Hamdije Čemerlića 2/VI, 71000 Sarajevo, Bosnia & Herzegovina

### 5.4 Data Subject Notification (GDPR Art. 34 / ZZPL Art. 57)

**Required if:** Breach likely to result in HIGH risk to data subjects
**Timeline:** Without undue delay
**Method:** Direct email to affected users — individual, not public announcement

**Financial data breach — assess HIGH risk if:**
- Tax IDs (PIB/JMBG/OIB/JIB) exposed — identity theft risk
- IBAN exposed — financial fraud risk
- Password hashes exposed (weak hashing) — credential stuffing risk
- Note: bcrypt-hashed passwords with cost=12 are computationally infeasible to crack — assess as LOW risk even if hashes exposed

### 5.5 Multi-Jurisdiction Notification Checklist

```
□ Identify all affected data subjects by country
□ If Croatian users affected → notify AZOP within 72h
□ If Serbian users affected → notify Poverenik within 72h
□ If BiH users affected → notify AZLP within 72h (best practice)
□ If HIGH risk to any users → notify affected users directly (no undue delay)
□ If DPA customers affected → notify per DPA contract terms
□ Document all notifications with timestamp and reference number
```

---

## 6. Communication Templates

### 6.1 Internal Incident Alert

```
Subject: [INCIDENT P{SEVERITY}] Security Incident — Bilko — {DATE} {TIME}

Team,

Security incident detected. Details:

Incident ID: INC-BILKO-{DATE}-{N}
Severity: P{SEVERITY}
Detected: {DATE} {TIME} UTC
Jurisdictions potentially affected: {RS / BA / HR}
Data potentially affected: {tax IDs / IBAN / credentials / invoice data}
Status: ACTIVE — Containment in progress

Incident Commander: Alem (CEO)
Incident channel: #incident-{DATE}-{N}

72h regulatory notification clock started: {DATE} {TIME} UTC
Notification deadlines:
  - AZOP (HR): {DATE+72h}
  - Poverenik (RS): {DATE+72h}
  - AZLP (BA): {DATE+72h}

Do NOT discuss on public channels, with customers, or social media.
All communications through Alem.

Next update: {TIME} UTC
```

### 6.2 Data Subject Notification (Croatian / English)

```
Subject: Važna sigurnosna obavijest o vašem računu / Important security notice

Poštovani {IME} / Dear {FIRST_NAME},

Bilko (bilko.io) vas obavještava o sigurnosnom incidentu koji je mogao utjecati
na vaš račun.

Što se dogodilo / What happened:
Dana {DATUM}, saznali smo za neovlašteni pristup našim sustavima koji je mogao
izložiti vaše osobne podatke.

Koji podaci su zahvaćeni / Data potentially affected:
{KATEGORIJE_PODATAKA}

Što smo poduzeli / What we did:
- {MJERA_1}
- {MJERA_2}

Što trebate učiniti / What you should do:
- Promijenite lozinku na bilko.io / Change your password at bilko.io
- Omogućite dvofaktorsku autentikaciju / Enable two-factor authentication
- Pratite sumnjive aktivnosti / Monitor for suspicious activity

Za više informacija / For more information:
DPO: dpo@bilko.io
Privacy: bilko.io/privacy

Ispričavamo se za uzrokovanu neugodnost.
We sincerely apologize for any concern this may cause.

Bilko tim / Bilko Team
DPO: dpo@bilko.io
```

### 6.3 Regulatory Notification Draft (GDPR Art. 33 — for AZOP, Poverenik, AZLP)

```
To: {AUTHORITY_NAME} — {AUTHORITY_EMAIL}
From: {DPO_NAME}, DPO — Bilko (bilko.io)
Date: {DATE} {TIME} UTC

NOTIFICATION OF PERSONAL DATA BREACH

1. Nature of the breach:
   On {DATE}, we became aware of {unauthorized access to / accidental disclosure of}
   personal data processed by Bilko. The breach {is/was} {ONGOING/CONTAINED}.

2. Categories of data subjects and approximate numbers:
   - Data subjects: {NUMBER} (estimated)
   - Categories: {user account data / tax IDs (PIB/JMBG/OIB/JIB) / IBAN / invoice data}
   - Records: {NUMBER} (estimated)
   - Countries of affected data subjects: {RS / BA / HR}

3. Contact details of DPO:
   Name: {DPO_NAME}
   Email: dpo@bilko.io
   Organization: Bilko (bilko.io)

4. Likely consequences:
   {ASSESSMENT — e.g., "Tax ID exposure creates risk of identity theft; IBAN exposure creates risk of financial fraud"}

5. Measures taken or proposed:
   Immediate: {CONTAINMENT_MEASURES}
   Ongoing: {ONGOING_MEASURES}
   Planned: {REMEDIATION_MEASURES}

6. Note: This is a {complete / preliminary — supplementary notification to follow} report.

[DPO Signature]
{DPO_NAME}
dpo@bilko.io
```

---

## 7. Evidence Preservation

```
Evidence checklist:
□ Railway log export: all services, 30-day window around incident
□ Cloudflare log export: WAF events, access logs
□ Sentry: error events export
□ LoggedAction query: all mutations during incident window
  SELECT * FROM logged_actions WHERE action_timestamp BETWEEN '{start}' AND '{end}'
□ PostgreSQL: pg_stat_activity snapshot, connection history
□ Authentication logs: failed login attempts, token usage

Chain of custody:
- SHA-256 hash all exported files immediately after collection
- Record: filename, hash, collector, timestamp
- Storage: encrypted archive, access restricted to IRT
```

---

## 8. Drill Schedule

| Drill Type | Frequency | Participants | Scenario |
|-----------|-----------|------------|---------|
| Tabletop exercise | Quarterly | Full IRT + CEO | Tax ID breach / IBAN exposure / credential stuffing |
| Notification drill | Semi-annual | DPO + Legal | 72-hour multi-jurisdiction notification dry run |
| Technical response | Annual | Engineering + Security | Simulated org-scope bypass in staging |

**Last drill:** Not yet conducted — schedule within 30 days of product launch
**Drill coordinator:** Compliance Architect (security@bilko.io)

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | Compliance Architect | 2026-02-23 | |
| DPO | | | |
| Legal Counsel | | | |
| CEO | | | |

# Key Management Policy

# Key Management Policy

> **Organization:** Bilko — Balkan Accounting SaaS
> **Policy Number:** POL-SEC-KM-001
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** CTO
> **Status:** Draft
> **Reviewers:** DPO, Engineering Lead
> **Classification:** Confidential — Restricted

## Document History

| Version | Date       | Author | Changes                                 |
| ------- | ---------- | ------ | --------------------------------------- |
| 0.1     | 2026-02-23 | CTO    | Initial key management policy for Bilko |

---

## 1. Purpose & Scope

This policy defines the lifecycle management for all cryptographic keys and secrets used by Bilko. It covers key generation, storage, rotation, revocation, and destruction.

**Scope:** All Bilko production and staging environments. All personnel with access to Railway environment variables or Vaultwarden.

**Field-level encryption scope:** FIELD_ENCRYPTION_KEY and FIELD_HMAC_KEY apply to JMBG and OIB fields only. PIB, JIB, and IBAN are NOT subject to field-level encryption per ADR-014 §2 (Tier 2 controls — disk-level encryption only).

---

## 2. Key Inventory

| Key ID               | Key Type                                      | Purpose                                                       | Storage                      | Rotation Period                    | Owner            |
| -------------------- | --------------------------------------------- | ------------------------------------------------------------- | ---------------------------- | ---------------------------------- | ---------------- |
| JWT_PRIVATE_KEY      | RSA 2048-bit private key                      | JWT access token signing (RS256)                              | Railway secret (production)  | Annual                             | CTO              |
| JWT_PUBLIC_KEY       | RSA 2048-bit public key                       | JWT access token verification                                 | Railway secret (production)  | Annual (with private)              | CTO              |
| REFRESH_TOKEN_SECRET | 64-byte random hex                            | Refresh token HMAC signing                                    | Railway secret               | Annual                             | CTO              |
| FIELD_ENCRYPTION_KEY | 32-byte random hex (AES-256)                  | Field-level encryption of JMBG and OIB in Contacts table only | Railway secret + Vaultwarden | Annual                             | CTO              |
| FIELD_HMAC_KEY       | 32-byte random hex                            | Org-scoped HMAC-SHA256 for jmbg_hash + oib_hash columns       | Railway secret + Vaultwarden | Annual (with FIELD_ENCRYPTION_KEY) | CTO              |
| DATABASE_URL         | PostgreSQL connection string with credentials | Database access                                               | Railway secret               | On compromise / quarterly review   | CTO              |
| SEF_API_KEY          | API key string                                | Serbia SEF e-invoice portal (per org)                         | DB (encrypted) per org       | Per SEF portal policy              | Per organization |
| FINA_CERT            | X.509 certificate + private key               | HR-FISK e-invoice signing (FINA PKI)                          | DB (encrypted) per org       | Per FINA PKI (1-3 years)           | Per organization |
| SENTRY_DSN           | DSN string                                    | Error tracking                                                | Railway secret / env var     | On compromise                      | CTO              |

---

## 3. Key Hierarchy

```mermaid
graph TD
    ROOT["Root Secrets\n(CTO personal Vaultwarden vault)"]
    RAILWAY["Railway Environment Secrets\n(production / staging / dev)"]
    ORG_SECRETS["Per-Organization Secrets\n(DB encrypted, L4 Restricted)\nSEF API keys, FINA certs"]
    APP["Application Runtime\n(keys loaded from env at startup)"]

    ROOT -->|"Provision"| RAILWAY
    RAILWAY -->|"Load at boot"| APP
    ROOT -->|"Rotation authority"| ORG_SECRETS
    ORG_SECRETS -->|"Decrypt on request"| APP
```

**Principle:** No key material is ever committed to source code. No key is stored in plaintext outside Railway secrets or Vaultwarden.

---

## 4. Key Generation Standards

| Key Type             | Generation Method                       | Entropy Requirements |
| -------------------- | --------------------------------------- | -------------------- |
| RSA (JWT)            | `openssl genrsa 2048`                   | 2048-bit minimum     |
| Symmetric (AES-256)  | `openssl rand -hex 32`                  | 256 bits (32 bytes)  |
| HMAC key             | `openssl rand -hex 32`                  | 256 bits             |
| Refresh token secret | `openssl rand -hex 64`                  | 512 bits             |
| API keys (external)  | Generated by external portal (SEF/FINA) | Per external system  |

**Commands:**

```bash
# Generate JWT key pair
openssl genrsa -out jwt_private.pem 2048
openssl rsa -in jwt_private.pem -pubout -out jwt_public.pem

# Generate AES-256 field encryption key
openssl rand -hex 32

# Generate HMAC key
openssl rand -hex 32
```

All generated keys must be imported to Railway and Vaultwarden within 1 hour. Local files deleted securely after import.

---

## 5. Key Storage

### Production Keys (Railway)

- All production keys stored as Railway environment variables
- Railway EU West region — encrypted at rest by Railway (AES-256)
- Access: CTO + one designated backup (CEO) only
- Two-factor authentication mandatory for Railway account
- Railway account uses ALAI SSO / strong password (≥20 chars, in Vaultwarden)

### Staging/Dev Keys

- Separate Railway project (staging) — different keys from production
- Dev: `.env.local` files excluded from git via `.gitignore`
- Dev keys may use weaker entropy but must still be valid format

### Vaultwarden (Backup & Documentation)

- URL: https://vault.basicconsulting.no
- Stores: production key material as secure notes (encrypted)
- Access: CTO + CEO (break-glass access)
- Purpose: Recovery if Railway secrets are lost; rotation documentation

### Per-Organization Secrets (SEF API Keys, FINA Certificates)

- Stored in PostgreSQL `OrganizationSecret` table
- Value encrypted with FIELD_ENCRYPTION_KEY before storage
- Decrypted in-memory only when needed for API call
- FINA private keys additionally protected with password (stored separately)

---

## 6. Key Rotation Procedures

### 6.1 Annual Rotation (Standard)

Schedule: First Monday of each calendar year.

**FIELD_ENCRYPTION_KEY rotation (most sensitive — requires re-encryption):**

```
1. Generate new FIELD_ENCRYPTION_KEY (openssl rand -hex 32)
2. Deploy a migration job that:
   a. Reads each encrypted field with old key
   b. Decrypts
   c. Re-encrypts with new key
   d. Writes back to DB
3. Migration must be atomic per record (read old → write new in transaction)
4. Only after 100% migration: update Railway secret to new key
5. Delete old key from Vaultwarden (add to archive note with date)
6. Test: attempt decryption with both old (should fail) and new (should succeed) keys
```

**JWT key pair rotation (zero-downtime):**

```
1. Generate new RSA key pair
2. Add new public key to JWKS endpoint alongside old (support both during rotation window)
3. Begin issuing new tokens signed with new private key
4. Wait for all old tokens to expire (15 minutes max)
5. Remove old public key from JWKS
6. Update JWT_PRIVATE_KEY and JWT_PUBLIC_KEY in Railway
7. Invalidate all refresh tokens (users will re-login)
```

### 6.2 Emergency Rotation (On Compromise)

If a key is suspected compromised:

1. **Immediately** invalidate: all user sessions (clear RefreshToken table)
2. Generate new key within 15 minutes
3. Update Railway secret
4. Deploy new application instance (Railway auto-deploys on env var change)
5. Document in Vaultwarden: old key, date of compromise, date of rotation
6. Assess whether breach notification is required (see data-breach-response-plan.md)

### 6.3 FINA Certificate (HR-FISK) Rotation

FINA X.509 certificates for HR-FISK e-invoicing have a defined validity period (1-3 years per FINA PKI).

```
1. FINA certificate expiry alert fires 60 days before expiry
2. Organization admin is notified to renew via FINA portal
3. New certificate uploaded through Bilko settings → HR eRačun → Certificate
4. Old certificate archived (not deleted — needed to verify past submissions)
5. Test: submit a test e-invoice via HR-FISK test environment with new certificate
```

---

## 7. Key Access Control

| Key                  | Who Can Access                 | How                                                                 |
| -------------------- | ------------------------------ | ------------------------------------------------------------------- |
| JWT_PRIVATE_KEY      | Application only (Railway env) | Never exposed via API; loaded at startup                            |
| FIELD_ENCRYPTION_KEY | Application only               | Never logged; never returned in API response                        |
| DATABASE_URL         | Application + CTO              | Railway secret; CTO can view in Railway dashboard                   |
| SEF API keys         | Application + org owner        | Decrypted only for SEF API calls; org owner can rotate via settings |
| FINA certificates    | Application + org owner        | Decrypted only for HR-FISK submissions                              |

**Access log:** All Railway secret views logged in Railway audit trail. Any access outside normal deployment is reviewed by CTO.

---

## 8. Escrow & Recovery

### FIELD_ENCRYPTION_KEY Escrow (Critical)

The FIELD_ENCRYPTION_KEY is the most critical key — loss means permanent loss of all L4 Restricted field data (tax IDs, IBAN).

**Escrow procedure:**

- FIELD_ENCRYPTION_KEY stored in Vaultwarden secure note accessible to: CTO, CEO
- Vaultwarden has its own backup (see system infrastructure docs)
- Key material noted with: creation date, rotation date, description

**If FIELD_ENCRYPTION_KEY is lost and not recoverable:** All encrypted field data is permanently unreadable. This is a catastrophic data loss event. Contact legal counsel and affected supervisory authorities.

### Railway Account Recovery

- Railway root account: admin@bilko.io (password in Vaultwarden)
- 2FA recovery codes: Vaultwarden secure note
- Designated backup access: CEO has view access to Railway (read-only)

---

## 9. Key Destruction

When a key is retired (superseded by rotation):

1. Remove from Railway environment variables
2. Remove from active Vaultwarden entries
3. Archive to Vaultwarden secure note: "Retired Keys" with date and reason
4. Old FIELD_ENCRYPTION_KEY versions: retained for 3 months after rotation (in case rollback needed), then permanently deleted from Vaultwarden

---

## 10. Securion Sign-off Requirement

No changes to FIELD_ENCRYPTION_KEY, FIELD_HMAC_KEY, or field-level encryption implementation may be deployed to production without written approval from Parisa Tabriz (Securion).

**Process:**

1. All field encryption code changes must complete Securion security review
2. Review conducted against checklist: `docs/security/FieldEncryptionSecurionChecklist.md`
3. Sign-off evidence file required before `mc.js done` on any field encryption task
4. Evidence file stored in: `docs/security/securion-approvals/YYYY-MM-DD-<task-id>.md`

**Scope:** This requirement applies to:

- Initial field encryption implementation (MC #9966)
- Any changes to FieldEncryption.kt, FieldHmac.kt, FieldEncryptionRotationScript.kt
- Database migration changes affecting jmbg, oib, jmbg_hash, oib_hash columns
- Key rotation procedures
- Addition of new encrypted fields

**Exemptions:** Changes to non-cryptographic code (UI masking, API response filtering) do not require Securion sign-off but must still undergo Proveo QA review.

---

## Approval

| Role                        | Name | Signature | Date       |
| --------------------------- | ---- | --------- | ---------- |
| Author                      | CTO  |           | 2026-02-23 |
| Reviewer (DPO)              |      |           |            |
| Reviewer (Engineering Lead) |      |           |            |
| Approver                    | CEO  |           |            |

# Security Testing Policy

# Security Testing Policy

> **Updated 2026-04-29 for ADR-021 path realignment.** See `docs/architecture/ADR-021-bilko-blueprint-section-15-realignment.md`.

> **Organization:** Bilko — Balkan Accounting SaaS
> **Policy Number:** POL-SEC-TEST-001
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** CTO / Security Engineer
> **Status:** Draft
> **Reviewers:** Engineering Lead, DPO
> **Classification:** Confidential

## Document History

| Version | Date       | Author | Changes                                   |
| ------- | ---------- | ------ | ----------------------------------------- |
| 0.1     | 2026-02-23 | CTO    | Initial security testing policy for Bilko |

---

## 1. Purpose & Scope

This policy defines the security testing requirements, tools, schedule, and acceptance criteria for the Bilko platform. Bilko handles regulated financial data (tax IDs, IBAN, accounting records) across three jurisdictions. Security testing is mandatory, not optional.

**Scope:** All Bilko applications — Express API (`apps/api-express/`), Next.js frontend (`apps/web/`), database layer (Prisma + PostgreSQL), and external integrations (SEF, HR-FISK). The Kotlin/Ktor backend (`apps/api/`) is covered separately as it matures (MC #5125).

---

## 2. Security Testing Pyramid

```mermaid
graph TD
    subgraph AUTOMATED["Automated (runs every CI pipeline)"]
        SAST["SAST\nESLint Security Rules\nTypeScript strict mode\nnpm audit\nSnyk SCA"]
        UNIT["Security Unit Tests\nVitest\nRBAC matrix tests\nOrg isolation tests\nEncryption tests\nVAT accuracy tests"]
        INT["Integration Tests\nVitest + Supertest\nAuth flow tests\nJWT validation\nRate limiting tests"]
    end

    subgraph PERIODIC["Periodic (scheduled)"]
        DAST["DAST\nOWASP ZAP\nMonthly + pre-release"]
        E2E["Security E2E\nPlaywright\nCross-tenant boundary tests\nPrivilege escalation tests"]
    end

    subgraph MANUAL["Manual (scheduled)"]
        PENTEST["Penetration Test\nExternal vendor\nAnnual"]
        REVIEW["Security Code Review\nPre-merge (security-sensitive PRs)\nArchitecture review quarterly"]
    end

    UNIT --> INT --> DAST --> PENTEST
```

---

## 3. Automated Security Testing (CI/CD)

Every push to `main` and every pull request triggers:

### 3.1 Static Analysis (SAST)

| Tool                              | What It Checks                                                   | Failure Threshold                      |
| --------------------------------- | ---------------------------------------------------------------- | -------------------------------------- |
| ESLint + `eslint-plugin-security` | Common JS security patterns (eval, RegExp DoS, object injection) | Any `error` level finding blocks merge |
| TypeScript strict mode            | Type safety prevents implicit `any` that could bypass validation | Build failure blocks merge             |
| `npm audit --audit-level=high`    | Known vulnerabilities in dependencies                            | HIGH or CRITICAL CVEs block merge      |
| Snyk (optional Phase 2)           | Deeper SCA including license compliance                          | CRITICAL blocks merge                  |

### 3.2 Security Unit Tests (Vitest)

Location: `apps/api-express/src/__tests__/security/`

**Required test suites:**

#### RBAC Matrix Tests

```typescript
// Every permission combination must be explicitly tested
describe('RBAC — Invoice access', () => {
  const roles = ['owner', 'admin', 'accountant', 'viewer']

  test.each([
    ['owner', 'create', true],
    ['admin', 'create', true],
    ['accountant', 'create', true],
    ['viewer', 'create', false],
    ['owner', 'delete', true],
    ['admin', 'delete', true],
    ['accountant', 'delete', false],
    ['viewer', 'delete', false],
  ])('role=%s action=%s expected=%s', async (role, action, expected) => {
    const token = signTestJWT({ role, org: 'org-1' })
    const res = await request(app).post(`/api/invoices`).set('Authorization', `Bearer ${token}`)
    // check response matches expected
  })
})
```

#### Organization Isolation Tests (Multi-Tenant Critical)

```typescript
describe('Org isolation — no cross-tenant data leak', () => {
  let org1Token: string
  let org2InvoiceId: string

  beforeAll(async () => {
    // Setup two orgs with data
    org1Token = signTestJWT({ org: 'org-1', role: 'owner' })
    const org2Token = signTestJWT({ org: 'org-2', role: 'owner' })

    // Create invoice in org-2
    const res = await request(app)
      .post('/api/invoices')
      .set('Authorization', `Bearer ${org2Token}`)
      .send(validInvoicePayload)
    org2InvoiceId = res.body.id
  })

  test('org-1 cannot read org-2 invoice', async () => {
    const res = await request(app)
      .get(`/api/invoices/${org2InvoiceId}`)
      .set('Authorization', `Bearer ${org1Token}`)
    expect(res.status).toBe(404) // NOT 403 — don't reveal existence
  })

  test('org-1 list does not include org-2 data', async () => {
    const res = await request(app).get('/api/invoices').set('Authorization', `Bearer ${org1Token}`)
    const ids = res.body.data.map((i: any) => i.id)
    expect(ids).not.toContain(org2InvoiceId)
  })
})
```

#### Field Encryption Tests

```typescript
describe('Field encryption — L4 Restricted', () => {
  test('PIB stored encrypted in DB', async () => {
    const testPIB = '123456789' // fake PIB
    // Create contact with PIB
    await request(app)
      .post('/api/contacts')
      .set('Authorization', `Bearer ${ownerToken}`)
      .send({ name: 'Test', taxId: testPIB, type: 'RS' })

    // Read raw DB value — should not be plaintext
    const raw = await prisma.$queryRaw`
      SELECT "taxId" FROM "Contact" WHERE name = 'Test'
    `
    expect(raw[0].taxId).not.toBe(testPIB)
    expect(raw[0].taxId).toMatch(/^[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*$/)
    // Should be base64:base64:base64 format (iv:authTag:ciphertext)
  })

  test('decrypted PIB matches original on read', async () => {
    const res = await request(app).get('/api/contacts').set('Authorization', `Bearer ${ownerToken}`)
    const contact = res.body.data.find((c: any) => c.name === 'Test')
    expect(contact.taxId).toBe('123456789')
  })
})
```

#### VAT Accuracy Tests (Financial Compliance)

```typescript
describe('VAT calculation accuracy', () => {
  test('RS: VAT 20% on standard goods (NUMERIC precision)', () => {
    const net = new Decimal('100.00')
    const vatAmount = net.mul('0.20')
    const gross = net.plus(vatAmount)
    expect(vatAmount.toString()).toBe('20.00')
    expect(gross.toString()).toBe('120.00')
  })

  test('HR: VAT 25% (EUR since Jan 2024)', () => {
    const net = new Decimal('100.00')
    const gross = net.mul('1.25')
    expect(gross.toString()).toBe('125.00')
  })

  test('BA: VAT 17% (UIO standard)', () => {
    const net = new Decimal('100.00')
    const gross = net.mul('1.17')
    expect(gross.toString()).toBe('117.00')
  })

  test('No float drift on invoice totals', () => {
    // Known JS float bug: 0.1 + 0.2 !== 0.3
    const line1 = new Decimal('0.10')
    const line2 = new Decimal('0.20')
    expect(line1.plus(line2).toString()).toBe('0.30')
    // Contrast: expect(0.1 + 0.2).toBe(0.3) would FAIL
  })
})
```

#### Authentication Tests

```typescript
describe('Auth — JWT security', () => {
  test('expired access token returns 401', async () => {
    const expiredToken = signTestJWT({ exp: Math.floor(Date.now() / 1000) - 1 })
    const res = await request(app)
      .get('/api/invoices')
      .set('Authorization', `Bearer ${expiredToken}`)
    expect(res.status).toBe(401)
  })

  test('tampered token returns 401', async () => {
    const validToken = signTestJWT({ role: 'viewer' })
    // Tamper: change role claim in payload
    const parts = validToken.split('.')
    const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString())
    payload.role = 'owner' // attempt privilege escalation
    parts[1] = Buffer.from(JSON.stringify(payload)).toString('base64url')
    const tamperedToken = parts.join('.')
    const res = await request(app)
      .delete('/api/invoices/any-id')
      .set('Authorization', `Bearer ${tamperedToken}`)
    expect(res.status).toBe(401)
  })

  test('rate limiting: 6th auth attempt in 15min returns 429', async () => {
    for (let i = 0; i < 5; i++) {
      await request(app).post('/api/auth/login').send({ email: 'x', password: 'wrong' })
    }
    const res = await request(app).post('/api/auth/login').send({ email: 'x', password: 'wrong' })
    expect(res.status).toBe(429)
  })
})
```

### 3.3 Dependency Scanning

```yaml
# .github/workflows/security.yml
- name: Audit dependencies
  run: npm audit --audit-level=high
  # HIGH or CRITICAL CVEs fail the build

- name: Check for secrets in code
  uses: trufflesecurity/trufflehog@main
  # Scans for committed credentials, API keys
```

**Dependency update policy:**

- CRITICAL CVE: patch within 24 hours
- HIGH CVE: patch within 7 days
- MEDIUM CVE: patch within 30 days
- LOW CVE: patch at next sprint boundary

---

## 4. Dynamic Application Security Testing (DAST)

### OWASP ZAP

**Schedule:** Monthly + before every major release

**Scope (in-scope for ZAP):**

- `https://staging.bilko.io` (staging environment only — NEVER production)
- All API endpoints under `/api/`
- Authentication flows
- File upload endpoints (if any)

**Out of scope:**

- SEF portal, FINA portal (external systems)
- Railway infrastructure
- Cloudflare WAF (managed by Cloudflare)

**ZAP Configuration:**

```yaml
# zap-baseline.yaml
env:
  contexts:
    - name: Bilko API
      urls:
        - https://staging.bilko.io/api/
      authentication:
        method: script
        # ZAP script to authenticate and get JWT
  rules:
    - id: 10202 # Absence of Anti-CSRF tokens — note (cookies are httpOnly)
      threshold: LOW
    - id: 10096 # Timestamp Disclosure — ignore (timestamps are public)
      threshold: OFF
```

**Required ZAP findings threshold (before release):**

- CRITICAL / HIGH: 0 allowed
- MEDIUM: must be assessed — known acceptable risks documented
- LOW / INFORMATIONAL: document and prioritize

---

## 5. Penetration Testing

**Frequency:** Annual (or after significant architecture change)
**Provider:** External certified pentest firm (OSCP/CREST certified)

**Scope:**

- Web application (app.bilko.io)
- API endpoints
- Authentication & session management
- Multi-tenant isolation (primary focus — org isolation must be tested)
- Business logic flaws (VAT calculation, invoice numbering)
- Third-party integrations (SEF API, HR-FISK)

**Rules of engagement:**

- Staging environment only — no production testing without explicit CEO approval
- No DoS / DDoS testing
- No social engineering of employees
- Penetration test agreement signed before engagement begins

**Remediation SLAs (post-pentest findings):**
| Severity | Fix Deadline |
|---------|-------------|
| CRITICAL | 48 hours |
| HIGH | 7 days |
| MEDIUM | 30 days |
| LOW | Next quarter |

---

## 6. Security Code Review

**When required (mandatory pre-merge):**

- Changes to authentication or authorization code
- Changes to encryption utilities (`encryptField`, `decryptField`)
- Changes to Prisma query patterns (potential org isolation bypass)
- New external API integrations (SEF, FINA, etc.)
- Changes to RBAC middleware or permission matrices

**Who reviews:** CTO or designated Senior Engineer with security background.

**Checklist for security-sensitive PRs:**

- [ ] No secrets or credentials in code or config
- [ ] All new Prisma queries include `organizationId` in WHERE clause
- [ ] New endpoints have RBAC decorator applied
- [ ] New user inputs validated with Zod schema
- [ ] L4 Restricted fields encrypted before write, decrypted after read
- [ ] LoggedAction entry created for all write operations
- [ ] Rate limiting applied to new auth-adjacent endpoints

---

## 7. CI/CD Security Gates

```mermaid
flowchart LR
    PR["Pull Request"] --> LINT["ESLint Security\nRules"]
    LINT -->|"PASS"| AUDIT["npm audit\n--audit-level=high"]
    AUDIT -->|"PASS"| TEST["Vitest\nSecurity Test Suite"]
    TEST -->|"PASS"| SECRETS["TruffleHog\nSecret Scan"]
    SECRETS -->|"PASS"| MERGE["Merge Allowed"]
    LINT -->|"FAIL"| BLOCK["PR Blocked"]
    AUDIT -->|"FAIL"| BLOCK
    TEST -->|"FAIL"| BLOCK
    SECRETS -->|"FAIL"| BLOCK
```

**Non-negotiable gates (cannot be bypassed with `--no-verify` or `--force`):**

1. ESLint security rules: zero `error` findings
2. npm audit: zero HIGH/CRITICAL CVEs
3. Vitest security tests: 100% pass — especially org isolation and RBAC tests
4. TypeScript strict: zero type errors

---

## 8. Vulnerability Disclosure

**Process:**

1. Security researchers may report vulnerabilities to security@bilko.io
2. Acknowledgment within 24 hours
3. Investigation and severity assessment within 5 business days
4. Remediation per SLA in Section 5
5. Responsible disclosure: researcher notified when fix is deployed

---

## Approval

| Role                        | Name                    | Signature | Date       |
| --------------------------- | ----------------------- | --------- | ---------- |
| Author                      | CTO / Security Engineer |           | 2026-02-23 |
| Reviewer (Engineering Lead) |                         |           |            |
| Reviewer (DPO)              |                         |           |            |
| Approver                    | CEO                     |           |            |

# Security Architecture Document

# Security Architecture Document

> **Project:** Drop — Fintech Payment App (Remittance + QR Payments)
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** ALAI Security Team
> **Status:** Draft
> **Reviewers:** CISO, CTO, DPO
> **Classification:** Confidential

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1     | 2026-02-12 | Security Agent (ALAI) | Initial security audit |
| 1.0     | 2026-02-23 | Security Architect (ALAI) | Architecture documentation |

---

## 1. Security Architecture Overview

**Security Owner:** CISO (Alem Bašić / ALAI Holding AS)
**Last Security Review:** 2026-02-12 (full audit); 2026-02-13 (hardening verification)
**Next Scheduled Review:** 2027-02-12 (annual) or after Phase 2 integration (BankID, Open Banking)
**Compliance Targets:** GDPR | PSD2 (Betalingstjenesteloven) | AML/Hvitvaskingsloven | DORA/IKT-forskriften | Finanstilsynet license

**Architecture Model:** Drop operates a **PSD2 pass-through model**. Drop never holds customer funds. AISP reads bank balances via Open Banking; PISP initiates payments directly from the user's bank account. Cards are a future feature, gated behind feature flags (all default to `false`).

**Security Posture Summary (post-hardening 2026-02-13):**
- 0 Critical findings remaining (all 4 resolved)
- 0 High findings remaining (all resolved)
- 2 Medium findings remaining (CSP `unsafe-inline`, proxy X-Forwarded-For trust)
- 4 Low findings acknowledged (out of scope for current MVP sprint)

### Defense-in-Depth Overview

```
Internet
  → WAF (planned — Phase 2 infra hardening)
  → CDN / Edge TLS termination (planned)
  → Load Balancer (TLS 1.3)
  → Application Layer (Next.js — Next.js 16.1.6)
      ├── Rate Limiting (SQLite-backed, persistent)
      ├── Origin/CSRF validation
      ├── JWT auth + session revocation
      ├── RBAC (user / merchant roles)
      ├── Input validation + sanitization
      └── Parameterized SQL queries
  → Database Layer (SQLite — MVP; PostgreSQL planned Phase 2)
      └── Stored: bcrypt(password), session token hashes, masked card tokens

Monitoring: Sentry (error tracking) — SIEM planned Phase 3
```

---

## 2. Authentication Flows

### 2.1 Current MVP Authentication (Email + Password)

```
User → POST /api/auth/login {email, password}
     → Rate limit check (10 req/60s per IP — SQLite-backed)
     → SELECT user WHERE email = ?
     → bcrypt.verify(password, hash) [cost factor 12]
     → If valid: generate JWT (HS256, jose ^6.1.3, 24h expiry)
     → INSERT into sessions table (token_hash = SHA-256(token))
     → Set httpOnly cookie (secure:true, sameSite:strict, maxAge:24h)
     → Return 200
```

**Source:** `src/drop-app/src/lib/auth.ts`, `src/drop-app/src/lib/middleware.ts`

### 2.2 Session Lifecycle

| Step | Action | Source |
|------|--------|--------|
| Login | Session created, token_hash stored | `auth.ts:56-65` |
| Each request | Session checked for revocation | `middleware.ts:66-74` |
| Logout | All user sessions revoked server-side | `auth/logout/route.ts:5-14` |
| Password change | All sessions revoked | Planned (Phase 2) |

**Session table schema:**
| Column | Type | Purpose |
|--------|------|---------|
| `id` | TEXT PK | `ses_<hex16>` format |
| `user_id` | TEXT FK | References `users.id` |
| `token_hash` | TEXT | SHA-256 of JWT token |
| `expires_at` | TEXT | Expiration timestamp |
| `revoked` | INTEGER | 0 = active, 1 = revoked |

### 2.3 BankID OIDC Authentication (Phase 2 — Planned)

BankID is not yet integrated in the MVP codebase. Required for PSD2 SCA compliance before any live transactions. Planned integration:

```
User → Drop App → BankID OIDC (nivå høyt — eIDAS Level High)
     → BankID returns: name, fødselsnummer (national ID), verified identity
     → Drop validates: age >= 18 (from fødselsnummer), Norwegian residency
     → Dynamic linking for payment authorization (amount + payee bound to auth)
```

**Regulatory requirement:** PSD2 (Betalingstjenesteloven §§ 4-28, 4-29) requires SCA with two of three factors. BankID covers possession + knowledge. No live transactions without this.

**Integration partner:** TBD — BankID Norge AS (DPA required)

### 2.4 KYC Flow (Phase 2 — Planned via Sumsub)

Current state: Mock KYC with auto-approve (`kyc_status` field in `users` table). Production will use Sumsub:

```
User → Sumsub SDK → Document scan + liveness check
     → Sumsub webhook → Drop backend
     → Update users.kyc_status = 'approved'/'rejected'
     → Required for: remittance transactions
```

**Source:** `legal/dpa-sumsub.md`, `legal/dpia-vurdering.md`

---

## 3. Authorization Model

**Model:** RBAC (Role-Based Access Control) with resource-level user scoping

### 3.1 Roles

| Role | Description | Access |
|------|-------------|--------|
| `user` | Standard registered user | Own data only (transactions, recipients, notifications, settings) |
| `merchant` | Merchant with QR payment dashboard | Own data + merchant dashboard (`/api/merchant/*`) |

**KYC Status** (enforced gate for financial operations):
| Status | Meaning | Effect |
|--------|---------|--------|
| `pending` | Default on registration | Cannot initiate remittance |
| `approved` | KYC completed | Full access to financial features |
| `rejected` | KYC failed or blocked | Blocked from all financial operations |

### 3.2 Resource-Level Access Control (IDOR Prevention)

All data access queries include `AND user_id = ?` to scope data to the authenticated user. Applied to:
- `recipients` — scoped to user
- `transactions` — scoped to user
- `bank_accounts` — scoped to user
- `notifications` — scoped to user
- `settings` — scoped to user
- `cards` — scoped to user (future feature)

Merchant endpoints verify both merchant role and ownership.

**Source:** `src/drop-app/src/app/api/` — all route handlers

### 3.3 Permission Summary

| Resource | `user` | `merchant` | Admin (TBD) |
|---------|--------|------------|-------------|
| Own profile | CRUD | CRUD | — |
| Own transactions | Read | Read | — |
| Own recipients | CRUD | CRUD | — |
| Merchant dashboard | — | Read | — |
| All users | — | — | Admin only |
| AML reports | — | — | Compliance only |

---

## 4. Data Encryption

### 4.1 Encryption at Rest

| Data | Method | Status |
|------|--------|--------|
| Database (SQLite) | OS-level (filesystem) | MVP — migrate to PostgreSQL Phase 2 |
| Passwords | bcrypt, cost factor 12 | Implemented (`bcryptjs ^3.0.3`) |
| Session tokens | SHA-256 hash stored (not plaintext) | Implemented |
| JWT secret | `JWT_SECRET` env var (fatal if missing in prod) | Implemented |
| Fødselsnummer (national ID) | AES-256-GCM application layer + HSM key | Planned Phase 2 |
| Card data | Only `last_four` + `token_ref` stored (PAN/CVV never stored) | Implemented (fix C1) |
| Bank account numbers | Only last 4 digits in API responses | Implemented |

**Note:** Field-level encryption for PII (fødselsnummer) requires HSM-backed key management (planned Phase 2 with AWS KMS).

### 4.2 Encryption in Transit

| Connection | Protocol | Status |
|-----------|---------|--------|
| User → Drop API | HTTPS / TLS 1.3 | Production requirement |
| Drop → BankID | HTTPS / TLS 1.3 | Phase 2 |
| Drop → Sumsub | HTTPS / TLS 1.3 | Phase 2 |
| Drop → Neonomics (PSD2) | HTTPS / TLS 1.3 | Phase 2 |
| Drop → Swan | HTTPS / TLS 1.3 | Phase 2 |
| Internal (service-to-service) | mTLS | Phase 3 (when microservices introduced) |

### 4.3 Cookie Security Configuration

**Source:** `src/drop-app/src/lib/auth.ts:48-54`

| Property | Value | Purpose |
|----------|-------|---------|
| `httpOnly` | `true` | Prevents JavaScript access (XSS mitigation) |
| `secure` | `true` (production) | HTTPS-only transport |
| `sameSite` | `"strict"` | CSRF prevention |
| `maxAge` | 86400 (24h) | Session lifetime |
| `path` | `"/"` | Full site scope |

---

## 5. Network Security

### 5.1 Current MVP Network Architecture

```
Internet → Next.js App (port 3000) → SQLite DB (local file)
```

**Phase 2 target:**
```
Internet
  → Cloudflare (DDoS + WAF)
  → AWS Load Balancer (TLS termination)
  → Private Subnet: Next.js App (ECS/Fargate)
  → Private Data Subnet: PostgreSQL (RDS)
  → External APIs: BankID, Sumsub, Neonomics, Swan (all HTTPS)
```

### 5.2 Security Groups (Phase 2 planned)

| Source | Destination | Port | Action |
|--------|------------|------|--------|
| Internet | Load Balancer | 443 | ALLOW |
| Internet | Any | 80 | REDIRECT → 443 |
| Load Balancer | App servers | 3000 | ALLOW |
| App servers | PostgreSQL | 5432 | ALLOW |
| App servers | External APIs | 443 | ALLOW (allowlist) |
| Any | Data Subnet | Any | DENY (default) |

### 5.3 Rate Limiting

**Source:** `src/drop-app/src/lib/middleware.ts:6-31`

| Endpoint Type | Limit | Window | Implementation |
|---------------|-------|--------|---------------|
| Auth routes (login, register) | 10 req | 60 seconds | SQLite-backed (persistent across restarts) |
| Transaction routes (remittance, qr-payment) | 10 req | 60 seconds | SQLite-backed |
| Rate routes (`/api/rates`) | 120 req | 60 seconds | SQLite-backed |

**Rate limit table:** `rate_limits` — per-IP tracking via `X-Forwarded-For` header.

**Known gap:** X-Forwarded-For can be spoofed. Fix requires trusted proxy validation (planned Phase 2 with load balancer).

---

## 6. API Security

### 6.1 Input Validation

**Source:** `src/drop-app/src/lib/middleware/validation.ts:149-203`

All API inputs validated at controller level:
- `sanitizeText()` — strips HTML tags, control characters, enforces max length
- `validateName()` — rejects XSS payloads, script tags
- `validateEmail()` — RFC format validation
- `validatePhone()` — international format
- `validateAmount()` — positive, finite, max 2 decimal places
- `validateIBAN()` — format + checksum
- `validatePIN()` — exactly 4 digits
- `validateCurrency()` — whitelist: EUR, USD, GBP, BAM, CHF, PLN, NOK, RSD, TRY, PKR
- `validateLanguage()` — whitelist: nb, en, bs, sq

**Amount limits:**
| Endpoint | Min | Max |
|----------|-----|-----|
| Remittance | 100 NOK | 50,000 NOK |
| QR Payment | 1 NOK | 100,000 NOK |

### 6.2 SQL Injection Prevention

All 24 API endpoints use **parameterized queries exclusively** (`?` placeholders). No string concatenation in SQL.

**Source:** `src/drop-app/src/app/api/` — all route handlers; verified in security audit 2026-02-12.

### 6.3 CSRF Protection

Origin header validation on all authenticated requests:
- Validates `Origin` against: `NEXT_PUBLIC_APP_URL`, `http://localhost:3000`, `http://localhost:3001`
- Combined with `sameSite: "strict"` cookies for defense-in-depth

**Source:** `src/drop-app/src/lib/middleware.ts:44-55`

### 6.4 Content Security Policy

**Source:** `src/drop-app/next.config.ts:6-46`

```http
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self';
  frame-ancestors 'none';
```

**Known limitation (Medium severity):** `unsafe-inline` and `unsafe-eval` required for Next.js dev mode. Production should use nonce-based CSP. Planned Phase 2.

---

## 7. OWASP Top 10 Mitigation Matrix

| OWASP Risk | Mitigation | Implementation | Status |
|-----------|-----------|---------------|--------|
| A01: Broken Access Control | RBAC + `AND user_id = ?` on all queries | All 24 API endpoints | Implemented |
| A02: Cryptographic Failures | bcrypt cost 12, JWT HS256, HTTPS TLS 1.3, httpOnly cookies | `auth.ts`, `utils-server.ts`, `next.config.ts` | Implemented |
| A03: Injection | Parameterized queries exclusively (no string SQL concat) | All API routes | Implemented |
| A04: Insecure Design | Pass-through model (no fund custody), feature flags for cards | Architecture, `feature-flags.ts` | Implemented |
| A05: Security Misconfiguration | Security headers, demo credentials gated (`NODE_ENV !== 'production'`) | `next.config.ts`, `db.ts` | Implemented |
| A06: Vulnerable Components | All deps recent, no known CVEs (audit 2026-02-12) | `package.json` | Implemented |
| A07: Auth Failures | bcrypt hashing, session revocation, rate limiting, no SHA-256 legacy | `auth.ts`, `middleware.ts`, `utils-server.ts` | Implemented |
| A08: Software Integrity | TBD — signed commits planned Phase 3 | — | Planned |
| A09: Logging Failures | Sentry (error tracking) — audit log table planned Phase 3 | MVP: Sentry | Partial |
| A10: SSRF | Pass-through model limits outbound surface; allowlist planned Phase 2 | Architecture | Partial |

---

## 8. Security Headers Checklist

**Source:** `src/drop-app/next.config.ts`

| Header | Value | Status |
|--------|-------|--------|
| `Strict-Transport-Security` | `max-age=63072000; includeSubDomains; preload` | Implemented (fix M2) |
| `Content-Security-Policy` | See §6.4 | Partial — `unsafe-inline` remaining |
| `X-Content-Type-Options` | `nosniff` | Implemented |
| `X-Frame-Options` | `DENY` | Implemented |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Implemented |
| `Permissions-Policy` | `camera=(self), microphone=(), geolocation=(self)` | Implemented |
| `Cache-Control` (auth responses) | `no-store` | TBD — Phase 2 |

---

## 9. Dependency Vulnerability Management

**Last dependency review:** 2026-02-12 (security audit)

| Package | Version | Risk Assessment |
|---------|---------|----------------|
| `jose` | ^6.1.3 | Low — Well-maintained JWT library |
| `bcryptjs` | ^3.0.3 | Low — Pure JS bcrypt |
| `better-sqlite3` | ^12.6.2 | Low — Parameterized queries |
| `next` | 16.1.6 | Low — Recent version |
| `react` | 19.2.3 | Low — Latest major |
| `radix-ui` | ^1.4.3 | Low — UI components only |

**Remediation SLAs:**
| Severity | SLA |
|---------|-----|
| Critical (CVSS ≥ 9.0) | 24 hours |
| High (CVSS 7.0-8.9) | 7 days |
| Medium (CVSS 4.0-6.9) | 30 days |
| Low (CVSS < 4.0) | 90 days |

**Planned:** Dependabot + Snyk integration in CI/CD pipeline (Phase 3).

---

## 10. Security Logging & Audit Trail

**Current (MVP):** Sentry for error tracking. No structured audit log table.

**Planned (Phase 3):** Audit log table + SIEM integration.

| Event | Planned Logging | Alert? |
|-------|----------------|--------|
| Login success | user_id, ip, user_agent, timestamp | No |
| Login failure | ip, email_hash, attempt_count, timestamp | Yes (> 5 failures) |
| Session revocation | user_id, timestamp, reason | Yes |
| Transaction initiated | user_id, amount, currency, corridor, timestamp | No |
| KYC status change | user_id, old_status, new_status, timestamp | Yes |
| AML flag triggered | user_id, rule, transaction_id, timestamp | Yes |
| Password change | user_id, ip, timestamp | Yes |

**AML transaction monitoring thresholds** (from `legal/hvitvaskingsrutiner.md`):
- Single transaction > NOK 50,000 → manual review
- Daily cumulative > NOK 100,000 → manual review
- Monthly cumulative > NOK 500,000 → EDD assessment
- Structuring patterns → automatic flag

---

## 11. Integration Security

### Third-Party Services (Phase 2)

| Service | Purpose | Auth Method | Data Shared | DPA |
|---------|---------|------------|-------------|-----|
| BankID Norge AS | SCA + Identity verification | OIDC | Name, fødselsnummer | Required |
| Sumsub | KYC/AML document verification | API key + webhook HMAC | ID documents, liveness data | Signed (`legal/dpa-sumsub.md`) |
| Swan | Banking / payment rails | OAuth 2.0 | Transaction data | Signed (`legal/dpa-swan.md`) |
| Neonomics | PSD2 AISP/PISP (Open Banking) | OAuth 2.0 (PSD2) | Bank account data, payment initiation | Required |
| Sentry | Error monitoring | DSN | Stack traces, user IDs | Signed (`legal/dpa-sentry.md`) |
| AWS | Infrastructure | IAM roles + KMS | Infrastructure only | AWS DPA |

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | ALAI Security Team | 2026-02-23 | |
| CISO / Security Lead | TBD — requires appointment | | |
| DPO | TBD — requires appointment | | |
| CTO | Alem Bašić | | |

# Security Architecture

# Security Architecture Document

> **Project:** Bilko — Balkan Accounting SaaS
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** Compliance Architect
> **Status:** Draft
> **Reviewers:** CTO, DPO, Engineering Lead
> **Classification:** Confidential

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1 | 2026-02-23 | Compliance Architect | Initial draft — Bilko security architecture |

---

## 1. Security Architecture Overview

**Security Owner:** Compliance Architect (security@bilko.io)
**Last Security Review:** 2026-02-23
**Next Scheduled Review:** 2026-08-23
**Compliance Targets:** GDPR | Zakon o zaštiti podataka o ličnosti RS (ZZPL) | Zakon o zaštiti ličnih podataka BiH (ZZLP) | GDPR via AZOP (HR) | Zakon o računovodstvu RS/HR/BA | Zakon o PDV RS/BA/HR

**Architecture Model:** Bilko is a multi-tenant cloud accounting SaaS. Processes invoices, expenses, VAT returns, and financial reports for organizations in Serbia, Bosnia & Herzegovina, and Croatia. Each organization's data strictly isolated by `organizationId` at the database layer.

### Defense-in-Depth Overview

```mermaid
graph TD
    CLIENT["Client Browser / PWA"]

    subgraph NETWORK["Network Layer"]
        CF["Cloudflare WAF\nDDoS Protection\nTLS 1.3 termination\nHSTS"]
    end

    subgraph APP_LAYER["Application Layer"]
        HELMET["Helmet.js\nCSP + X-Frame + HSTS\nno X-Powered-By"]
        CORS["CORS Whitelist\nbilko.io only\nno wildcard *"]
        RATE["Rate Limiter\nexpress-rate-limit\n5 req/15min auth\n100 req/15min general"]
        AUTH_MW["Auth Middleware\nJWT verify (15min access)\norg-scope injection"]
        RBAC_MW["RBAC Middleware\nowner / admin / accountant / viewer"]
        ZOD["Zod Validation\nall request bodies\ntype-safe parsing"]
    end

    subgraph DATA_LAYER["Data Layer"]
        PRISMA_ORM["Prisma ORM\nparameterized queries\nno raw SQL for user input\norg-scoped WHERE clauses"]
        PG_ENC["PostgreSQL (Railway EU West)\nAES-256 disk encryption\nbackup encryption"]
    end

    subgraph AUDIT["Audit Layer"]
        LOG["LoggedAction table\nAPPEND-ONLY\nIP + user + timestamp\nold/new values (changedFields)"]
    end

    CLIENT --> CF --> HELMET --> CORS --> RATE --> AUTH_MW --> RBAC_MW --> ZOD --> PRISMA_ORM --> PG_ENC
    PRISMA_ORM --> LOG
```

---

## 2. Authentication

### 2.1 Strategy: JWT (JSON Web Tokens)

Stateless JWT, scales horizontally on Railway. Access tokens (15 min, memory-only) + refresh tokens (7 days, httpOnly cookie). Rotation on every refresh. Revocation via hashed token storage in DB.

### 2.2 JWT Auth Flow

```mermaid
sequenceDiagram
    actor User
    participant FE as Frontend (bilko.io — Vercel)
    participant API as Express API (api.bilko.io — Railway EU)
    participant DB as PostgreSQL (Railway EU West)

    User->>FE: Enter email + password
    FE->>API: POST /api/v1/auth/login
    API->>DB: SELECT user WHERE email = ? (parameterized)
    DB-->>API: User record (passwordHash)
    API->>API: bcrypt.compare(password, hash) — 12 rounds
    alt Password valid
        API->>API: jwt.sign({sub, org, role}, JWT_SECRET, 15m)
        API->>DB: INSERT refreshToken (hashed, expiresAt)
        API-->>FE: 200 { accessToken } + Set-Cookie: refreshToken (httpOnly, secure, sameSite=strict)
        FE->>FE: Store accessToken in memory only
    else Password invalid
        API-->>FE: 401 Unauthorized (generic — no user enumeration)
    end

    Note over FE,API: 15 minutes later — access token expires
    FE->>API: POST /api/v1/auth/refresh (Cookie: refreshToken)
    API->>API: Rotate: delete old, issue new
    API-->>FE: 200 { newAccessToken } + Set-Cookie: newRefreshToken

    Note over User,DB: Logout
    FE->>API: POST /api/v1/auth/logout
    API->>DB: DELETE refreshToken WHERE userId = ?
    API-->>FE: 204 No Content
```

### 2.3 Two-Factor Authentication (2FA)

**Method:** TOTP (RFC 6238) — Google Authenticator, Authy, 1Password
- Setup: `POST /api/v1/auth/2fa/setup` → QR code + base32 secret
- Verify: `POST /api/v1/auth/2fa/verify { code }`
- Login: returns `{ requires2FA: true, tempToken }` → `POST /api/v1/auth/2fa/login`
- Backup: 10 single-use codes, bcrypt-hashed

---

## 3. Authorization (RBAC)

### 3.1 Role Permission Matrix

| Action | owner | admin | accountant | viewer |
|--------|-------|-------|------------|--------|
| Create invoice | ✅ | ✅ | ❌ | ❌ |
| Edit invoice | ✅ | ✅ | ❌ | ❌ |
| Delete invoice | ✅ | ❌ | ❌ | ❌ |
| View invoice | ✅ | ✅ | ✅ | ✅ |
| Approve expense | ✅ | ✅ | ❌ | ❌ |
| Generate report | ✅ | ✅ | ✅ | ❌ |
| Invite user | ✅ | ❌ | ❌ | ❌ |
| Edit org settings | ✅ | ❌ | ❌ | ❌ |

### 3.2 Organization Scoping (IDOR Prevention)

```typescript
// Injected by auth middleware on all /api/v1/* routes
app.use('/api/v1/*', (req, res, next) => {
  req.prismaWhere = { organizationId: req.user.organizationId };
  next();
});

// Applied to every Prisma query
await prisma.invoice.findMany({ where: { ...req.prismaWhere } });
```

UUID primary keys throughout — no sequential ID enumeration possible.

---

## 4. Encryption

### 4.1 In Transit: TLS 1.3

All traffic HTTPS. Cloudflare TLS 1.3 at edge, re-encrypted to Railway. HSTS: `max-age=63072000; includeSubDomains; preload`.

### 4.2 At Rest: AES-256

| Store | Method | Location |
|-------|--------|---------|
| PostgreSQL | AES-256 TDE (Railway) | Railway EU West (Frankfurt/Paris) |
| PostgreSQL backups | AES-256 auto-backup | Railway EU West — 30 days |
| Tax IDs (PIB/JMBG/OIB/JIB), IBAN | AES-256-GCM field encryption | Application layer — Railway env secret |
| Cloudflare R2 (receipts, PDFs) | AES-256 server-side | Cloudflare EU region |

### 4.3 Password Security

bcrypt, 12 salt rounds. Min 8 chars. Block top 10K common passwords. Last 5 hashes retained.

### 4.4 Financial Data Precision

All monetary amounts: `NUMERIC(19,4)` — never float. Exchange rates locked at transaction date.

---

## 5. OWASP Top 10 Mitigations

| OWASP Risk | Mitigation | Status |
|-----------|-----------|--------|
| A01: Broken Access Control | RBAC + org-scoped WHERE + UUID PKs | Designed |
| A02: Cryptographic Failures | TLS 1.3 + AES-256 + bcrypt(12) + no PII in JWT | Designed |
| A03: Injection | Prisma ORM parameterized queries exclusively | Designed |
| A04: Insecure Design | Multi-tenant org isolation at DB layer, immutable audit | Designed |
| A05: Security Misconfiguration | Helmet.js, CORS whitelist (no *), sanitized errors | Designed |
| A06: Vulnerable Components | Dependabot + weekly npm audit + lock file | Planned |
| A07: Auth Failures | Rate limiting + JWT rotation + 2FA + bcrypt(12) | Designed |
| A08: Software Integrity | Signed commits + CI/CD + Dependabot | Planned |
| A09: Logging Failures | Immutable LoggedAction table + Railway logs + Sentry | Designed |
| A10: SSRF | Zod validation + allowlist for SEF/HR-FISK/FINA API | Designed |

---

## 6. Rate Limiting

| Endpoint | Limit | Window |
|----------|-------|--------|
| POST /api/v1/auth/login | 5 req | 15 min |
| POST /api/v1/auth/register | 3 req | 60 min |
| POST /api/v1/auth/refresh | 10 req | 15 min |
| GET /api/v1/reports/* | 10 req | 15 min |
| All other /api/v1/* | 100 req | 15 min |

---

## 7. Input Validation (Zod)

```typescript
const createInvoiceSchema = z.object({
  customerId: z.string().uuid(),
  invoiceDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  currencyCode: z.enum(['EUR', 'RSD', 'BAM']),
  items: z.array(z.object({
    description: z.string().min(1).max(500),
    quantity: z.number().positive(),
    unitPrice: z.number().nonnegative(),
    taxRate: z.number().min(0).max(100),
  })),
});
```

---

## 8. File Upload Security

Allowed: JPG, PNG, PDF. Max 10 MB. MIME + extension validation. Stored in Cloudflare R2 EU. Phase 2: ClamAV scanning.

---

## 9. Audit Trail — LoggedAction (APPEND-ONLY)

| Field | Description |
|-------|-------------|
| eventId | Auto-incrementing |
| tableName | Mutated table |
| action | INSERT / UPDATE / DELETE |
| userId | Actor |
| actionTimestamp | UTC |
| rowData | Full row before mutation |
| changedFields | `{ field: { old: X, new: Y } }` |
| clientIp | Requester IP |

On GDPR erasure: userId → `"deleted-user"`. Financial entries retained 11 years (law). LoggedAction never deleted.

---

## 10. Security Headers (Helmet.js)

| Header | Value |
|--------|-------|
| Strict-Transport-Security | max-age=63072000; includeSubDomains; preload |
| Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline' |
| X-Content-Type-Options | nosniff |
| X-Frame-Options | DENY |
| X-Powered-By | Removed |

---

## 11. Pre-Launch Security Checklist

- [ ] JWT_SECRET generated (32+ chars, CSPRNG) — Railway env secret
- [ ] JWT_REFRESH_SECRET separate key (32+ chars)
- [ ] FIELD_ENCRYPTION_KEY generated (32 bytes hex) — for PIB/JMBG/OIB/JIB + IBAN
- [ ] HTTPS enforced
- [ ] CORS: bilko.io only
- [ ] Rate limiting tested
- [ ] Helmet.js headers verified
- [ ] bcrypt rounds = 12
- [ ] All Prisma queries use org-scoped WHERE
- [ ] Zod validation on all endpoints
- [ ] LoggedAction trigger active on all tables
- [ ] Error responses sanitized
- [ ] Dependabot alerts enabled
- [ ] Railway region = EU West confirmed
- [ ] DPAs signed (Railway, Vercel, Cloudflare, SendGrid)
- [ ] Data deletion workflow tested

---

## Related Documents
- Compliance Framework: [compliance-framework.md](compliance-framework.md)
- Data Encryption Policy: [data-encryption-policy.md](data-encryption-policy.md)
- Key Management Policy: [key-management-policy.md](key-management-policy.md)
- DPIA: [data-protection-impact-assessment.md](data-protection-impact-assessment.md)
- Breach Response: [data-breach-response-plan.md](data-breach-response-plan.md)
- Security Testing: [security-testing-policy.md](security-testing-policy.md)
- Bilko Security Docs: [../../products/Bilko/docs/security/](../../products/Bilko/docs/security/)

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | Compliance Architect | 2026-02-23 | |
| CTO | | | |
| DPO | | | |
| Engineering Lead | | | |

# Compliance Framework

# Compliance Framework Document

> **Project:** Bilko — Balkan Accounting SaaS
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** Compliance Architect
> **Status:** Draft
> **Reviewers:** DPO, Legal Counsel, CEO
> **Classification:** Confidential

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1 | 2026-02-23 | Compliance Architect | Initial draft — RS/BA/HR three-country compliance mapping |

---

## 1. Applicable Regulations

**Compliance Owner:** Compliance Architect (compliance@bilko.io)
**Last Review:** 2026-02-23 | **Next Review:** 2026-08-23

| Regulation | Country | Phase |
|-----------|---------|-------|
| GDPR — Regulation (EU) 2016/679 | HR | Phase 1 |
| Zakon o zaštiti podataka o ličnosti (ZZPL, Sl. glasnik RS 87/2018) | RS | Phase 2 |
| Zakon o zaštiti ličnih podataka BiH (ZZLP, Sl. glasnik BiH 49/2006) | BA | Phase 3 |
| Zakon o računovodstvu (Sl. glasnik RS 73/2019) | RS | Phase 2 |
| Zakon o računovodstvu i reviziji FBiH (Sl. novine FBiH 83/2009) | BA (FBiH) | Phase 3 |
| Zakon o računovodstvu i reviziji RS BiH (Sl. glasnik RS BiH 96/2005) | BA (RS entity) | Phase 3 |
| Zakon o računovodstvu HR (NN 78/15, 120/16, 116/18) | HR | Phase 2 |
| Zakon o PDV RS (Sl. glasnik RS 84/2004 et al.) | RS | Phase 2 |
| Zakon o PDV BiH (Sl. glasnik BiH 9/2005 et al.) | BA | Phase 3 |
| Zakon o porezu na dodanu vrijednost HR (NN 73/13 et al.) | HR | Phase 2 |
| Zakon o elektronskom dokumentu RS (Sl. glasnik RS 51/2009) | RS | Phase 2 |
| Opći porezni zakon HR (NN 115/16 et al.) | HR | Phase 2 |
| Pravilnik o kontnom okviru RS (2021) | RS | Phase 2 |
| FBiH Pravilnik o kontnom okviru (2022) | BA (FBiH) | Phase 3 |
| RRiF Kontni plan HR | HR | Phase 2 |

---

## 2. Serbia (RS) — Regulatory Compliance

### 2.1 Data Protection — Zakon o zaštiti podataka o ličnosti (ZZPL)

**Full name:** Zakon o zaštiti podataka o ličnosti
**Citation:** Sl. glasnik RS br. 87/2018
**In force:** November 21, 2018
**Description:** Serbia's GDPR-aligned personal data protection law.
**Supervisory authority:** Poverenik za informacije od javnog značaja i zaštitu podataka o ličnosti
**Website:** https://www.poverenik.rs

| Requirement | ZZPL Article | Bilko Implementation |
|------------|-------------|---------------------|
| Lawful basis for processing | Art. 12 | Contract (Art. 12 st. 1 tač. 2) — accounting service |
| Data minimization | Art. 5 st. 1 tač. 3 | Email, name, PIB/JMBG only where legally required |
| Data subject rights | Art. 26-41 | GET /account/data, DELETE /account, GET /account/export |
| Processing register | Art. 50 | Internal processing register required |
| Security of processing | Art. 50 | TLS 1.3, AES-256, bcrypt, RBAC |
| Breach notification to Poverenik | Art. 56 | Within 72 hours of awareness |

**Breach notification:** office@poverenik.rs | Bulevar kralja Aleksandra 15, 11000 Belgrade

### 2.2 Accounting Law — Zakon o računovodstvu

**Full name:** Zakon o računovodstvu
**Citation:** Sl. glasnik RS br. 73/2019, 44/2021

| Requirement | Bilko Implementation |
|------------|---------------------|
| Double-entry bookkeeping | Schema enforces debitAccountId + creditAccountId |
| Chart of accounts: Pravilnik o kontnom okviru (2021) — 10 class (0-9) | Serbian CoA seed data |
| Bilans stanja (Balance Sheet) + Bilans uspeha (Income Statement) | Phase 2 reports |
| Filing: APR (https://www.apr.gov.rs), deadline June 30 | PDF export + reminders |
| Document retention: **10 years** | Soft delete — never hard delete financial data |

### 2.3 VAT — Zakon o PDV

**Citation:** Sl. glasnik RS br. 84/2004 (consolidated)

| Rate | Description |
|------|-------------|
| 20% (opšta stopa) | Standard — general goods and services |
| 10% (snižena stopa) | Reduced — food, medicines, utilities |
| 0% | Exports, international transport |

**VAT threshold:** 8,000,000 RSD | **Return:** Monthly (>50M RSD) or Quarterly | **Deadline:** 15th of next month

### 2.4 E-Invoice — SEF (Sistem e-Faktura)

**Platform:** https://efaktura.gov.rs | **Mandatory:** B2B since January 2023
**Format:** UBL 2.1 XML | **Penalties:** 50,000–2,000,000 RSD for non-compliance
**Integration:** `@bilko/country-rs` package (Phase 2)

### 2.5 APR Filing

Serbian entities file annual financial reports with APR (Agencija za privredne registre). Deadline: June 30. Bilko generates APR-compatible PDF/XML exports.

---

## 3. Bosnia & Herzegovina (BA) — Regulatory Compliance

**Complexity:** BiH has two entities (FBiH and Republika Srpska). VAT unified at state level via UIO. Direct taxes separate per entity.

### 3.1 Data Protection — Zakon o zaštiti ličnih podataka BiH (ZZLP)

**Full name:** Zakon o zaštiti ličnih podataka Bosne i Hercegovine
**Citation:** Sl. glasnik BiH br. 49/2006, 76/2011, 89/2011
**Supervisory authority:** AZLP — Agencija za zaštitu ličnih podataka Bosne i Hercegovine
**Website:** https://www.azlp.ba

| Requirement | ZZLP Article | Bilko Implementation |
|------------|-------------|---------------------|
| Lawful basis | Art. 4 | Contract + legal obligation |
| Security measures | Art. 14 | TLS 1.3, AES-256, bcrypt, RBAC |
| Cross-border transfer | Art. 18 | Railway EU West — SCCs mechanism |
| Breach notification to AZLP | Art. 14 + GDPR practice | 72 hours |

**Breach notification:** info@azlp.ba | Hamdije Čemerlića 2/VI, 71000 Sarajevo

### 3.2 FBiH — Accounting Law

**Full name:** Zakon o računovodstvu i reviziji Federacije Bosne i Hercegovine
**Citation:** Sl. novine FBiH br. 83/2009, 56/2023

| Requirement | Bilko Implementation |
|------------|---------------------|
| Double-entry bookkeeping | Schema enforced |
| Chart of accounts: FBiH Pravilnik (2022) | BiH CoA seed data |
| Filing: Agency of Financial Information (FBiH), deadline March 31 | PDF export |
| Document retention: **10 years** | Immutable storage |

### 3.3 Republika Srpska (BA Entity)

**Citation:** Sl. glasnik RS BiH br. 96/2005, 74/2016
**Filing:** Tax Administration of RS (BiH entity), March 31
**Retention: 11 years** — maximum applied across BA entities

### 3.4 VAT — Zakon o PDV BiH

**Citation:** Sl. glasnik BiH br. 9/2005 (consolidated)
**Authority:** UIO — Uprava za indirektno oporezivanje | https://www.uino.gov.ba

| Rate | Description |
|------|-------------|
| 17% (opća stopa) | Standard — all goods and services |
| 0% | Exports |

**Threshold:** 100,000 BAM | **Return:** Monthly | **No reduced rates**

### 3.5 E-Invoice — CPF (Central Platform for Fiscalisation)

**Status:** PENDING — technical specifications not published
**Law adopted:** January 2026 (FBiH only)
**Expected:** ~2027

**Bilko decision:** DO NOT implement CPF until specs published. BiH is Phase 3 launch.

### 3.6 Corporate Income Tax

| Entity | Rate | Deadline |
|--------|------|---------|
| FBiH | 10% | March 31 |
| RS (BiH entity) | 10% | March 31 |

---

## 4. Croatia (HR) — Regulatory Compliance

**Note:** Croatia is EU member state. GDPR applies directly.

### 4.1 Data Protection — GDPR

**Applicable:** GDPR Regulation (EU) 2016/679 (directly applicable)
**National implementing act:** Zakon o provedbi Opće uredbe (NN 42/2018)
**Supervisory authority:** AZOP — Agencija za zaštitu osobnih podataka | https://azop.hr

| Requirement | GDPR Article | Bilko Implementation |
|------------|-------------|---------------------|
| Lawful basis | Art. 6 | Contract (6.1.b) for service; legal obligation (6.1.c) for tax |
| Data minimization | Art. 5(1)(c) | OIB, name, email only |
| Right to access | Art. 15 | GET /api/v1/account/data |
| Right to erasure | Art. 17 | DELETE /api/v1/account |
| Right to portability | Art. 20 | GET /api/v1/account/export |
| Security of processing | Art. 32 | TLS 1.3, AES-256, bcrypt, RBAC |
| Breach notification to AZOP | Art. 33 | Within 72 hours |
| DPA with processors | Art. 28 | Railway, Vercel, Cloudflare, SendGrid |

**Breach notification:** azop@azop.hr | https://azop.hr/prijavapovrede | Selska cesta 136, 10000 Zagreb

### 4.2 Accounting Law — Zakon o računovodstvu HR

**Citation:** NN 78/15, 120/16, 116/18, 42/20

| Requirement | Bilko Implementation |
|------------|---------------------|
| Double-entry bookkeeping | Schema enforced |
| Chart of accounts: RRiF standard | HR CoA seed data |
| Accounting standards: CFRS (SMEs) or IFRS (PIEs) | CFRS-compliant reports |
| Bilanca + Račun dobiti i gubitka | Report generation Phase 2 |
| Filing: FINA RGFI (https://www.fina.hr), deadline April 30 | FINA-compatible export |
| Document retention: **11 years** | Immutable storage |

### 4.3 General Tax Law — Opći porezni zakon HR

**Citation:** NN 115/16, 106/18, 121/19, 32/20
Document retention 11 years, electronic record acceptance, digital accounting system obligations.

### 4.4 VAT — Zakon o PDV HR

**Citation:** NN 73/13 et al. | **Portal:** ePorezna — https://www.porezna-uprava.hr

| Rate | Description |
|------|-------------|
| 25% (opća stopa) | Standard — general goods and services |
| 13% (srednja stopa) | Intermediate — foods, water, accommodation |
| 5% (snižena stopa) | Reduced — books, baby food, medicines |
| 0% | Exports, intra-EU supply |

**Threshold:** 60,000 EUR | **Return:** Monthly | **Deadline:** Last day of next month

### 4.5 E-Invoice — HR-FISK / eRačun

**Platform:** https://hr-fisk.fina.hr | **Operator:** FINA — Financijska agencija
**Mandatory since:** January 1, 2026 (all B2B, B2G, B2C)
**Format:** UBL 2.1 XML with HR-CIUS | **Protocol:** AS4 (Peppol-compatible)
**Certificate:** FINA qualified certificate required
**Penalties:** Up to EUR 500,000 for non-compliance
**Archive:** 11 years

**Integration:** `@bilko/country-hr` — FINA certificate + API (Phase 2)

### 4.6 Corporate Income Tax — Croatia

- Standard rate: 18% | Reduced: 10% (revenue <1M EUR) | Deadline: April 30

---

## 5. Cross-Country Compliance Matrix

| Requirement | Serbia (RS) | Bosnia & Herzegovina (BA) | Croatia (HR) |
|-------------|------------|--------------------------|-------------|
| Data protection law | ZZPL (GDPR-aligned, 2018) | ZZLP BiH (2006) | GDPR (directly applicable) |
| Supervisory authority | Poverenik | AZLP | AZOP |
| Breach notification deadline | 72 hours (ZZPL Art. 56) | 72 hours (best practice) | 72 hours (GDPR Art. 33) |
| VAT standard rate | 20% | 17% | 25% |
| VAT reduced rate | 10% | None | 13% / 5% |
| E-invoice platform | SEF (mandatory Jan 2023) | CPF (pending ~2027) | HR-FISK (mandatory Jan 2026) |
| E-invoice format | UBL 2.1 XML | TBD | UBL 2.1 XML (HR-CIUS) |
| Annual report filing | APR — June 30 | Agency Fin. Info / Tax Admin — March 31 | FINA RGFI — April 30 |
| Chart of accounts | Pravilnik (2021) | FBiH Pravilnik (2022) | RRiF standard |
| Document retention | **10 years** | **10 (FBiH) / 11 (RS entity)** | **11 years** |
| Currency | RSD | BAM | EUR |
| CIT rate | 15% | 10% | 18% (10% <1M EUR) |

**Bilko retention policy:** Apply maximum across all markets — **11 years** for all financial records. Never hard delete.

---

## 6. Data Classification Scheme

| Level | Label | Examples | Controls |
|-------|-------|---------|---------|
| L1 | Public | Exchange rates, fee schedule, privacy policy | None |
| L2 | Internal | Aggregated analytics, non-PII logs | Access control |
| L3 | Confidential | Email, name, organization data, invoice amounts | Encryption + access control + audit |
| L4 | Restricted | PIB/JMBG/OIB/JIB (tax IDs), IBAN, TOTP secrets, password hashes | Encryption + RBAC + MFA + audit + 11-year retention |

**Tax ID types by country:**
- Serbia: PIB (9 digits), JMBG (13 digits)
- BiH: JIB (13 digits)
- Croatia: OIB (11 digits)

---

## 7. Data Subject Rights Implementation

| Right | Endpoint | SLA | Exception |
|-------|---------|-----|-----------|
| Access (GDPR Art. 15 / ZZPL Art. 26) | GET /api/v1/account/data | 30 days | — |
| Rectification (Art. 16) | PATCH /api/v1/account/profile | Immediate | — |
| Erasure (Art. 17) | DELETE /api/v1/account | 30 days | Financial records retained per law |
| Portability (Art. 20) | GET /api/v1/account/export | 30 days | — |
| Restriction (Art. 18) | compliance@bilko.io | 30 days | Manual |

**Erasure exception:** Invoices, expenses, transactions retained 10-11 years (accounting law). Only PII (email, name, password hash) anonymized.

---

## 8. Third-Party Data Processors

| Processor | Service | Region | DPA Status |
|---------|---------|--------|-----------|
| Railway | PostgreSQL hosting | EU West (Frankfurt/Paris) | Required — sign before launch |
| Vercel | Frontend hosting | EU edge | Required |
| Cloudflare | CDN, WAF, R2 storage | EU region | Required |
| SendGrid | Transactional email | EU | Required |

---

## 9. Compliance Roadmap

### Phase 1 — Pre-Launch (GDPR baseline)
- [ ] Privacy policy published
- [ ] Terms of Service published
- [ ] User consent mechanism at registration
- [ ] Data deletion + anonymization workflow
- [ ] Data export endpoint
- [ ] DPAs signed: Railway, Vercel, Cloudflare, SendGrid
- [ ] Railway EU West region confirmed
- [ ] Breach notification process ready

### Phase 2 — Serbia Launch + Croatia Launch
**Serbia:**
- [ ] Legal review (accounting law + ZZPL)
- [ ] Serbian CoA seed data (Pravilnik 2021)
- [ ] VAT at 20% / 10%
- [ ] SEF XML export + API integration
- [ ] APR report export (Bilans stanja, Bilans uspeha)

**Croatia:**
- [ ] Legal review (Zakon o računovodstvu + GDPR)
- [ ] Croatian CoA seed data (RRiF)
- [ ] VAT at 25% / 13% / 5%
- [ ] FINA certificate for HR-FISK
- [ ] HR-FISK API integration (mandatory)
- [ ] FINA RGFI report export

### Phase 3 — BiH Launch
- [ ] Legal review (FBiH + RS entity distinction)
- [ ] BiH CoA seed data (FBiH Pravilnik 2022)
- [ ] VAT at 17% (UIO)
- [ ] Monitor CPF specs (~2027)
- [ ] FBiH vs RS entity org settings

---

## 10. Risk Assessment

| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| GDPR/ZZPL breach fine | Low (if compliant) | High (GDPR €20M / ZZPL RSD 2M) | Full implementation before first customer |
| SEF non-compliance (RS) | Medium | High (RSD 2M) | Phase 2 SEF integration |
| HR-FISK non-compliance (HR) | High (if not integrated) | Critical (EUR 500K) | Phase 2 mandatory |
| Financial data loss | Low | Critical | 30-day Railway backups, immutable audit |
| Tax calculation error | Low | High | Configurable rates, NUMERIC precision, Zod |
| BiH CPF delay | Medium | Low | Phase 3 planned, not blocking RS/HR |

---

## Related Documents
- Security Architecture: [security-architecture.md](security-architecture.md)
- DPIA: [data-protection-impact-assessment.md](data-protection-impact-assessment.md)
- Breach Response Plan: [data-breach-response-plan.md](data-breach-response-plan.md)
- Bilko Compliance: [../../products/Bilko/docs/security/COMPLIANCE.md](../../products/Bilko/docs/security/COMPLIANCE.md)
- Serbia Regulatory: [../../products/Bilko/docs/regulatory/RS/README.md](../../products/Bilko/docs/regulatory/RS/README.md)
- BiH Regulatory: [../../products/Bilko/docs/regulatory/BA/README.md](../../products/Bilko/docs/regulatory/BA/README.md)
- Croatia Regulatory: [../../products/Bilko/docs/regulatory/HR/README.md](../../products/Bilko/docs/regulatory/HR/README.md)

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | Compliance Architect | 2026-02-23 | |
| DPO | | | |
| Legal Counsel | | | |
| CEO | | | |

# DPIA — Data Protection Impact Assessment

# Data Protection Impact Assessment (DPIA)

> **Project:** Bilko — Balkan Accounting SaaS
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** DPO
> **Status:** Draft — requires DPO sign-off before launch
> **Reviewers:** CTO, Legal Counsel, DPO
> **Classification:** Confidential

## Document History

| Version | Date       | Author | Changes                                |
| ------- | ---------- | ------ | -------------------------------------- |
| 0.1     | 2026-02-23 | DPO    | Initial DPIA for Bilko accounting SaaS |

---

## 1. DPIA Necessity Assessment

**Is this DPIA mandatory?** YES.

Bilko meets multiple high-risk criteria under GDPR Article 35 and equivalent provisions in ZZPL (Serbia Art. 54) and ZZLP BiH (Art. 17a):

| Criterion                                               | Applies | Reason                                                                           |
| ------------------------------------------------------- | ------- | -------------------------------------------------------------------------------- |
| Large-scale processing of sensitive data                | YES     | Tax IDs (PIB, JMBG, OIB, JIB) qualify as identification data processed at scale  |
| Systematic processing of personal data                  | YES     | Core business function — every user's financial data processed continuously      |
| Processing that determines access to financial services | YES     | Accounting data used for tax filings, credit applications, regulatory compliance |
| Multi-jurisdictional cross-border transfers             | YES     | RS/BA to EU host (Railway)                                                       |
| Vulnerable data subjects                                | PARTIAL | Some SMB owners may be natural persons with limited tech literacy                |

---

## 2. System Description

**System Name:** Bilko Cloud Accounting Platform
**Controller:** Bilko d.o.o. / Bilko d.o.o. Sarajevo / Bilko d.o.o. Zagreb (per jurisdiction)
**Processor(s):** Railway (hosting), Cloudflare (CDN/WAF), Sentry (error tracking)
**DPO Contact:** Alem Bašić — alem@alai.no — +47 40 47 42 51

**Purpose:** Provide cloud-based double-entry accounting, invoicing, expense tracking, VAT reporting, and e-invoicing integration (SEF for RS, HR-FISK for HR) to SMBs in Serbia, Bosnia & Herzegovina, and Croatia.

**Lawful basis:** Contract performance (Art. 6(1)(b)) for core accounting services; Legal obligation (Art. 6(1)(c)) for tax ID storage and retention periods.

---

## 3. Data Flows

```mermaid
flowchart LR
    subgraph USERS["Data Subjects"]
        OWNER["Business Owner\n(natural person)"]
        CLIENT["Client (Contact)\n(natural person or legal entity)"]
    end

    subgraph BILKO["Bilko Platform"]
        API["Express API\n(Railway EU West)"]
        DB["PostgreSQL\n(Railway EU West)"]
        AUDIT["LoggedAction\nAudit Table"]
    end

    subgraph EXTERNAL["External Integrations"]
        SEF["SEF Portal\n(Serbia — efaktura.mfin.gov.rs)"]
        HRFISK["HR-FISK\n(Croatia — FINA)"]
        CF["Cloudflare WAF"]
        SENTRY["Sentry\n(Error tracking)"]
    end

    OWNER -->|"Creates account\nEmail, name, OrgPIB"| API
    OWNER -->|"Creates invoice\nBuyer PIB/OIB/JIB/JMBG\nIBAN\nAmounts"| API
    CLIENT -->|"Receives invoice\n(email)"| OWNER
    API --> DB
    API --> AUDIT
    API -->|"e-invoice XML"| SEF
    API -->|"e-invoice XML + FINA cert"| HRFISK
    API -->|"All traffic"| CF
    API -->|"Error traces"| SENTRY
```

### Data Inventory

| Data Element                  | Source                    | Stored | Encrypted                                                                           | Retention        | Jurisdiction |
| ----------------------------- | ------------------------- | ------ | ----------------------------------------------------------------------------------- | ---------------- | ------------ |
| Email address                 | User registration         | YES    | No (indexed)                                                                        | Account lifetime | All          |
| Full name                     | User registration         | YES    | No                                                                                  | Account lifetime | All          |
| Organization name             | Registration              | YES    | No                                                                                  | 10-11 years      | All          |
| PIB (Serbia tax ID)           | Invoice creation          | YES    | **Disk encryption + API controls** (L4-B, See ADR-014)                              | 10 years         | RS           |
| JMBG (Serbia personal ID)     | Invoice — natural persons | YES    | **AES-256-GCM field-level** + HMAC-SHA256 hash (L4-A, See ADR-014)                  | 10 years         | RS           |
| OIB (Croatia personal tax ID) | Invoice creation          | YES    | **AES-256-GCM field-level** + HMAC-SHA256 hash (L4-A, See ADR-014)                  | 11 years         | HR           |
| JIB (BiH tax ID)              | Invoice creation          | YES    | **Disk encryption + API controls** (L4-B, See ADR-014)                              | 10-11 years      | BA           |
| IBAN                          | Bank accounts / invoices  | YES    | **Disk encryption + API masking** (last 4 digits in list views) (L4-B, See ADR-014) | 10-11 years      | All          |
| Invoice amounts               | Invoices                  | YES    | No (NUMERIC 19,4)                                                                   | 10-11 years      | All          |
| IP address                    | Session logs              | YES    | No                                                                                  | 30 days          | All          |
| Browser user agent            | Session logs              | YES    | No                                                                                  | 30 days          | All          |
| Audit trail entries           | System                    | YES    | No                                                                                  | 10-11 years      | All          |

---

## 4. Risk Assessment

### Risk Matrix

```
           LIKELIHOOD
           Low    Medium    High
      ┌────────┬─────────┬────────┐
High  │   M    │    H    │   C    │  C = Critical
      ├────────┼─────────┼────────┤  H = High
IMPACT│   L    │    M    │    H   │  M = Medium
Med   ├────────┼─────────┼────────┤  L = Low
      │   N    │    L    │    M   │
Low   └────────┴─────────┴────────┘
```

### Identified Risks

| Risk ID | Risk                                                          | Impact     | Likelihood | Rating | Mitigation                                                                                                        |
| ------- | ------------------------------------------------------------- | ---------- | ---------- | ------ | ----------------------------------------------------------------------------------------------------------------- |
| R-01    | Unauthorized access to personal IDs (JMBG/OIB)                | High       | Medium     | **H**  | AES-256-GCM field-level encryption (L4-A, ADR-014); RBAC restricts access                                         |
| R-01b   | Unauthorized access to business IDs (PIB/JIB)                 | Low-Medium | Medium     | **M**  | Disk encryption + org-scoping + RBAC (L4-B, ADR-014); PIB/JIB are publicly available on gov portals               |
| R-02    | Cross-tenant data leak (one org sees another's data)          | High       | Low        | **M**  | Prisma org-scoped WHERE on every query; automated test suite                                                      |
| R-03    | IBAN exposure enabling financial fraud                        | Medium     | Low        | **L**  | Disk encryption + API masking (last 4 digits in list views) (L4-B, ADR-014); IBAN is routinely shared for payment |
| R-04    | Breach of invoice data (amounts, buyer/seller details)        | High       | Low        | **M**  | TLS 1.3; Railway AES-256 at rest; RBAC                                                                            |
| R-05    | Railway data center compromise                                | High       | Very Low   | **L**  | Railway EU West (ISO 27001); DPA signed; encrypted backups                                                        |
| R-06    | Insufficient retention — legal/regulatory penalty             | High       | Medium     | **H**  | Retention lock prevents deletion; automated alerts before expiry                                                  |
| R-07    | Failed SEF/HR-FISK e-invoice — business disruption + fine     | High       | Medium     | **H**  | Test environment; idempotent submission; alert on failure                                                         |
| R-08    | Employee/insider access to client financial data              | Medium     | Low        | **L**  | RBAC; LoggedAction audit trail; background checks for staff                                                       |
| R-09    | Account takeover via credential stuffing                      | High       | Medium     | **H**  | bcrypt 12; rate limiting 5/15min auth; HIBP breach check                                                          |
| R-10    | JMBG processed without adequate legal basis                   | High       | Low        | **M**  | JMBG only accepted when user confirms natural person billing                                                      |
| R-11    | Cross-border transfer BA → Railway without adequate mechanism | Medium     | Medium     | **M**  | Standard Contractual Clauses with Railway for BiH users                                                           |

### Residual Risk Assessment

After applying controls in Section 5:

- R-01 (JMBG/OIB): Residual = **Low** (AES-256-GCM field-level encryption + RBAC, ADR-014 Tier 1)
- R-01b (PIB/JIB): Residual = **Low** (disk encryption + org-scoping; data is publicly available on gov registries)
- R-03 (IBAN): Residual = **Low** (disk encryption + API masking; IBAN is routinely shared for payment)
- R-06, R-07, R-09: Residual = **Medium** (operational dependencies remain)
- R-11: Residual = **Low** (SCC in place)

**Overall residual risk: MEDIUM — Acceptable with DPO sign-off.**

---

## 5. Mitigation Measures

| Control                                                        | Addresses   | Implementation                                                                                                                                               |
| -------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| AES-256-GCM field-level encryption for JMBG and OIB (L4-A)     | R-01, R-10  | `prisma-field-encryption` extension — `jmbg` and `oib` fields encrypted before write; `jmbgHash`/`oibHash` HMAC columns for exact-match lookup (See ADR-014) |
| Disk-level encryption + API controls for PIB, JIB, IBAN (L4-B) | R-01b, R-03 | Railway AES-256 disk encryption + org-scoping + RBAC; IBAN masked to last 4 digits in list responses (See ADR-014)                                           |
| Org-scoped WHERE on all Prisma queries                         | R-02        | Lint rule + automated isolation tests                                                                                                                        |
| JWT 15min access + 7day refresh + rotation                     | R-09        | Express auth middleware                                                                                                                                      |
| bcrypt cost factor 12                                          | R-09        | Password hashing on registration                                                                                                                             |
| Rate limiting: 5 auth req / 15min                              | R-09        | `express-rate-limit`                                                                                                                                         |
| HIBP breach check on registration                              | R-09        | k-anonymity API call                                                                                                                                         |
| LoggedAction audit trail (append-only)                         | R-08        | Prisma middleware — every write operation                                                                                                                    |
| Retention lock (10-11yr minimum)                               | R-06        | `deletedAt` check + age validation before hard delete                                                                                                        |
| DPA with Railway                                               | R-05        | Legal — sign before launch                                                                                                                                   |
| SCCs with Railway (for BiH users)                              | R-11        | Legal — sign before launch                                                                                                                                   |
| SEF/HR-FISK idempotent submission + retry                      | R-07        | API integration with deduplication key                                                                                                                       |
| JMBG consent gate                                              | R-10        | UI checkbox: "This invoice is for a natural person"                                                                                                          |

---

## 6. Consultation

### DPO Consultation

- DPO: Alem Bašić (alem@alai.no, +47 40 47 42 51, ALAI Holding AS org.nr 932 953 736)
- DPIA mandatory per GDPR Art. 35 — DPO must be consulted before processing begins
- DPO appointed: 2026-03-02 (jf. GDPR Art. 37-39 / personopplysningsloven)
- DPO opinion: [PENDING — sign-off required before production launch]

### Supervisory Authority Prior Consultation

Prior consultation required if residual risk remains HIGH after all mitigations.
Current assessment: MEDIUM — **prior consultation NOT required**, but this must be reasserted when HR-FISK and JMBG features are fully implemented.

### Data Subject Consultation

Consideration: SMB owners are sophisticated business users. DPIA does not require data subject consultation for B2B accounting software, but privacy policy must clearly explain tax ID processing.

---

## 7. Approval & Review

**DPO Sign-off Required Before:** Any feature that processes PIB, JMBG, OIB, JIB, or IBAN goes to production.

**Next DPIA Review:** When adding new data categories, new jurisdictions, or new external integrations.

| Role           | Name       | Signature | Date       |
| -------------- | ---------- | --------- | ---------- |
| Author         | Alem Bašić |           | 2026-02-23 |
| Reviewer (CTO) |            |           |            |
| DPO Approval   | Alem Bašić |           |            |
| CEO Sign-off   | Alem Bašić |           |            |

# Compliance Overview

# Bilko — Regulatory Compliance

> **Project:** Bilko
> **Version:** 1.0
> **Date:** 2026-02-25
> **Author:** ALAI Documentation Team
> **Status:** Final
> **Note:** Compliance implementation status is tracked per jurisdiction below. Document is finalized for Phase 1 MVP scope.

This document outlines regulatory compliance requirements for Bilko as a Balkan accounting SaaS.

---

## Compliance Scope

Bilko operates in a highly regulated space:

| Region                   | Regulations                                             |
| ------------------------ | ------------------------------------------------------- |
| **EU/EEA**               | GDPR (General Data Protection Regulation)               |
| **Serbia**               | Zakon o računovodstvu, SEF (Sistem E-Faktura)           |
| **Bosnia & Herzegovina** | Zakon o PDV-u, Electronic bookkeeping requirements      |
| **Croatia**              | Zakon o fiskalizaciji, eRačun (public sector invoicing) |

**Current Status:** MVP focuses on GDPR compliance. Balkan-specific regulations deferred to Phase 2.

---

## GDPR (General Data Protection Regulation)

### Applicability

- **Applies to:** All EU/EEA users (regardless of where Bilko is hosted)
- **Scope:** Personal data of natural persons (name, email, IP address)
- **Penalties:** Up to €20M or 4% of global turnover (whichever is higher)

### Data We Collect

| Data Type             | Purpose                | Legal Basis          | Retention                      |
| --------------------- | ---------------------- | -------------------- | ------------------------------ |
| **Email**             | Account authentication | Contract performance | Until account deletion         |
| **Full name**         | User identification    | Contract performance | Until account deletion         |
| **IP address**        | Security audit trail   | Legitimate interest  | 30 days                        |
| **Password (hashed)** | Authentication         | Contract performance | Until account deletion         |
| **Organization name** | Service delivery       | Contract performance | 5 years (accounting law)       |
| **Financial records** | Service delivery       | Legal obligation     | 5-10 years (varies by country) |

### GDPR Principles Compliance

#### 1. Lawfulness, Fairness, Transparency (Article 5(1)(a))

**Implementation:**

- Privacy policy visible before registration
- Terms of Service linked during signup
- Clear explanation of data usage
- No hidden data collection

**Status:** PLANNED — Privacy policy to be drafted

---

#### 2. Purpose Limitation (Article 5(1)(b))

**Implementation:**

- Data used only for stated purposes (accounting, invoicing)
- No data selling to third parties
- No marketing emails without explicit consent

**Status:** COMPLIANT (by design)

---

#### 3. Data Minimization (Article 5(1)(c))

**Implementation:**

- Only collect necessary data (email, name)
- No tracking cookies
- No analytics beyond server logs

**Status:** COMPLIANT (by design)

---

#### 4. Accuracy (Article 5(1)(d))

**Implementation:**

- Users can update profile (email, name)
- Users can correct financial data (invoices, expenses)

**Status:** COMPLIANT (by design)

---

#### 5. Storage Limitation (Article 5(1)(e))

**Implementation:**

- User data deleted on request (soft delete)
- Financial records retained 5 years (legal requirement overrides GDPR Article 17)
- Audit logs kept 30 days

**Status:** PLANNED — Deletion workflow to be implemented

---

#### 6. Integrity & Confidentiality (Article 5(1)(f))

**Implementation:**

- TLS 1.3 encryption in transit
- AES-256 encryption at rest
- bcrypt password hashing
- Access controls (RBAC)

**Status:** PLANNED — See [SECURITY-ARCHITECTURE.md](SECURITY-ARCHITECTURE.md)

---

### GDPR Rights (Articles 12-22)

#### Right to Access (Article 15)

**User can request:**

- Copy of all personal data
- Purpose of processing
- Data retention period

**Implementation:**

```typescript
// Endpoint: GET /api/v1/account/data
await prisma.user.findUnique({
  where: { id: userId },
  include: { organization: true, auditLogs: true },
})
```

**Status:** PLANNED

---

#### Right to Rectification (Article 16)

**User can:**

- Update email, name
- Correct invoices, expenses

**Implementation:**

```typescript
// Endpoint: PATCH /api/v1/account/profile
await prisma.user.update({
  where: { id: userId },
  data: { email, fullName },
})
```

**Status:** PLANNED

---

#### Right to Erasure (Article 17)

**Exceptions:**

- Financial records must be kept 5 years (legal obligation overrides)
- Audit logs anonymized (user ID replaced with "deleted-user")

**Implementation:**

```typescript
// Endpoint: DELETE /api/v1/account
await prisma.user.update({
  where: { id: userId },
  data: {
    email: `deleted-${userId}@example.com`,
    fullName: 'Deleted User',
    passwordHash: '',
    deletedAt: new Date(),
  },
})
```

**Status:** PLANNED

---

#### Right to Data Portability (Article 20)

**User can:**

- Export all data in JSON format

**Implementation:**

```typescript
// Endpoint: GET /api/v1/account/export
const data = {
  user: await prisma.user.findUnique({ where: { id: userId } }),
  invoices: await prisma.invoice.findMany({ where: { organizationId } }),
  expenses: await prisma.expense.findMany({ where: { organizationId } }),
}
res.json(data)
```

**Status:** PLANNED

---

#### Right to Object (Article 21)

**Not applicable** — Bilko does not use profiling or automated decision-making.

---

### Data Processing Agreement (DPA)

Required when Bilko processes customer data on behalf of organizations.

**Third-Party Processors:**

| Service        | Purpose             | DPA Available? | GDPR Compliant? |
| -------------- | ------------------- | -------------- | --------------- |
| **Railway**    | Database hosting    | Yes            | Yes (EU region) |
| **Vercel**     | Frontend hosting    | Yes            | Yes             |
| **Cloudflare** | R2 storage, DNS     | Yes            | Yes             |
| **SendGrid**   | Transactional email | Yes            | Yes             |

**Action Required:** Sign DPAs with all processors before launch.

**Status:** PENDING

---

### Data Breach Notification (Article 33)

**Requirement:**

- Notify supervisory authority within 72 hours of breach
- Notify affected users if high risk to rights and freedoms

**Process:**

1. Detect breach (monitoring, user report)
2. Assess impact (how many users, what data)
3. Contain breach (block attacker, revoke tokens)
4. Notify authority (within 72h)
5. Notify users (if high risk)
6. Document incident (post-mortem)

**Status:** PLANNED — Incident response plan documented in [SECURITY-ARCHITECTURE.md](SECURITY-ARCHITECTURE.md)

---

### Data Protection Officer (DPO)

**Required?** No — Bilko does not meet GDPR Article 37 criteria:

- Not a public authority
- Not large-scale systematic monitoring
- Not large-scale processing of sensitive data

**Threshold:** DPO required if >250 employees or large-scale processing. Bilko is small startup.

**Status:** NOT REQUIRED (as of 2026-02-20)

---

### Data Residency

**Requirement:** Store EU user data within EU/EEA (GDPR Article 44-50)

**Implementation:**

- Railway: EU West region (Frankfurt or Paris)
- Vercel: Edge network (serves from EU for EU users)
- Cloudflare R2: EU region

**Status:** PLANNED — Configure Railway to EU region on deployment

---

## Balkan Data Protection Laws

### Regulatory Comparison: RS / BA / HR

| Dimension                        | Serbia (RS)                                                              | Bosnia & Herzegovina (BA)                                            | Croatia (HR)                                      |
| -------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | ------------------------------------------------- |
| **Law**                          | ZZPL — Zakon o zaštiti podataka o ličnosti (Sl. glasnik RS 87/2018)      | ZZLP BiH — Zakon o zaštiti ličnih podataka (Sl. glasnik BiH 49/2006) | GDPR — Uredba (EU) 2016/679 (directly applicable) |
| **Model**                        | GDPR-aligned (adopted 2018, effective 2019)                              | Pre-GDPR, older framework (2006)                                     | Full EU GDPR — identical to GDPR                  |
| **Supervisory Authority**        | Poverenik za informacije od javnog značaja i zaštitu podataka o ličnosti | Agencija za zaštitu ličnih podataka (AZLP)                           | Agencija za zaštitu osobnih podataka (AZOP)       |
| **Authority Website**            | poverenik.rs                                                             | azlp.gov.ba                                                          | azop.hr                                           |
| **Notification Email**           | poverenik@poverenik.rs                                                   | azlp@azlp.gov.ba                                                     | azop@azop.hr                                      |
| **Max Penalty (legal entity)**   | 2,000,000 RSD (~€17,000)                                                 | 10,000 BAM (~€5,000)                                                 | €20,000,000 or 4% global annual turnover          |
| **Breach notification deadline** | 72 hours (ZZPL Art. 56 — GDPR Art. 33 equivalent)                        | Best practice 72 hours (ZZLP BiH less specific)                      | 72 hours (GDPR Art. 33)                           |
| **DPO Required?**                | No (same thresholds as GDPR Art. 37)                                     | No mandatory DPO provision                                           | No (same thresholds as GDPR Art. 37)              |
| **Legal basis for processing**   | Art. 12 ZZPL (mirrors GDPR Art. 6)                                       | Art. 5 ZZLP BiH                                                      | GDPR Art. 6 directly                              |

### Serbia ZZPL — Key Differences from GDPR

- **Article equivalences:** ZZPL Art. 26 = GDPR Art. 15 (access), Art. 27 = Art. 16 (rectification), Art. 28 = Art. 17 (erasure), Art. 30 = Art. 20 (portability)
- **Registration:** No requirement to register with Poverenik for standard processing (registration abolished in ZZPL 2018 reform)
- **Transfers:** Serbia recognized as adequate jurisdiction by European Commission (Decision 2023/1485 of July 21, 2023) — data can flow RS ↔ EU without additional mechanisms
- **Enforcement:** Poverenik has investigative and corrective powers; administrative fines up to 2M RSD; criminal liability for intentional violations

### BiH ZZLP — Key Differences from GDPR

- **Entity structure:** BiH has two entities (FBiH and RS entity) plus Brčko District — ZZLP BiH applies at state level, but FBiH and RS entity have their own accounting laws
- **Older law:** ZZLP BiH dates from 2006 — less specific on breach notification timing, no explicit DPO requirement, narrower data subject rights
- **No adequacy decision:** BiH is NOT on EU adequacy list. Cross-border transfers from BiH users to EU-hosted infrastructure require Standard Contractual Clauses (SCCs 2021/914)
- **AZLP powers:** Lower penalty ceiling (10K BAM) but can prohibit processing as sanction
- **Practical note:** BiH law reform expected ~2026-2027 to align with GDPR — monitor for updates

### Croatia GDPR — Implementation Notes

- **Full GDPR:** Croatia is EU member — GDPR applies directly since 2018. No separate Croatian data protection law needed
- **AZOP:** Croatian DPA; can issue fines up to €20M or 4% global turnover
- **Supplementing law:** Zakon o provedbi Opće uredbe o zaštiti podataka (NN 42/18) — national provisions for derogations (e.g., age of consent 16 years for online services)
- **HR-specific requirement:** Croatian law requires data processing records (čl. 30 GDPR) maintained in Croatian if requested by AZOP

---

## Data Retention Policy by Jurisdiction

### Retention Requirements — Financial & Accounting Records

| Data Category                    | Serbia (RS) | BiH — FBiH | BiH — RS Entity | Croatia (HR) | Legal Basis                                                                                            |
| -------------------------------- | ----------- | ---------- | --------------- | ------------ | ------------------------------------------------------------------------------------------------------ |
| **Financial statements**         | 10 years    | 10 years   | 10 years        | 11 years     | RS: Zakon o računovodstvu Art. 26; BA FBiH: Art. 17; BA RS: Art. 16; HR: Zakon o računovodstvu Art. 10 |
| **Invoices (issued & received)** | 10 years    | 10 years   | 10 years        | 11 years     | Same as above                                                                                          |
| **Bank account statements**      | 10 years    | 10 years   | 10 years        | 11 years     | Same as above + Opći porezni zakon (HR)                                                                |
| **Tax returns (VAT, CIT)**       | 10 years    | 10 years   | 10 years        | 11 years     | RS: Zakon o porezu na dodatu vrednost; HR: Opći porezni zakon Art. 92                                  |
| **Employee payroll records**     | 10 years    | 10 years   | 10 years        | 11 years     | Mandatory for pension/social security compliance                                                       |
| **Expense receipts**             | 10 years    | 10 years   | 10 years        | 11 years     | Same as invoices                                                                                       |
| **Audit trail (LoggedAction)**   | 10 years    | 10 years   | 10 years        | 11 years     | Derived from financial record retention                                                                |

### Retention Requirements — Personal Data (GDPR/ZZPL/ZZLP)

| Data Category                     | Retention Period                                 | Legal Basis                                                        |
| --------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------ |
| **User email, name**              | Account lifetime + 30 days after deletion        | Contract performance (GDPR Art. 6(1)(b))                           |
| **IP addresses, session logs**    | 30 days                                          | Legitimate interest (security) — minimal period                    |
| **Tax IDs (PIB, JMBG, OIB, JIB)** | 10–11 years                                      | Legal obligation — accounting/tax law overrides GDPR Art. 17(3)(b) |
| **IBAN numbers**                  | 10–11 years                                      | Legal obligation — same override                                   |
| **Backup copies**                 | Railway: 7-day automatic backup window           | Technical necessity                                                |
| **Deleted user account data**     | 30 days after soft delete (then hard delete PII) | Minimize retention per GDPR Art. 5(1)(e)                           |

### Retention Enforcement in Bilko

```typescript
// Delete-prevention lock — prevents hard delete during mandatory retention period
async function canDeleteFinancialRecord(recordId: string, createdAt: Date): Promise<boolean> {
  const jurisdiction = await getOrganizationJurisdiction(recordId)
  const retentionYears = jurisdiction === 'HR' ? 11 : 10 // BA RS entity is 11 too
  const cutoffDate = new Date()
  cutoffDate.setFullYear(cutoffDate.getFullYear() - retentionYears)

  if (createdAt > cutoffDate) {
    throw new Error(
      `Financial record cannot be deleted: retention period (${retentionYears} years) not elapsed`,
    )
  }
  return true
}
```

---

## Data Residency Requirements

### Primary Infrastructure

All Bilko production data is hosted in **Railway EU West** (Amsterdam or Frankfurt):

- **PostgreSQL database:** Railway EU West — encrypted at rest (AES-256)
- **File storage:** Cloudflare R2, EU region (Amsterdam)
- **CDN / WAF:** Cloudflare EU edge nodes serve EU users first
- **Error tracking:** Sentry (EU region SaaS) — configured for EU data residency

### Jurisdiction-Specific Requirements

| Jurisdiction                  | Data Residency Law  | Requirement                                                         | Bilko Implementation                      |
| ----------------------------- | ------------------- | ------------------------------------------------------------------- | ----------------------------------------- |
| **Croatia (HR)**              | GDPR Art. 44-50     | EU/EEA storage for personal data                                    | Railway EU West ✅                        |
| **Serbia (RS)**               | ZZPL Art. 64-70     | No mandatory localization; adequacy decision covers RS↔EU transfers | Railway EU West ✅ (adequacy covers this) |
| **Bosnia & Herzegovina (BA)** | ZZLP BiH Art. 14-17 | No explicit localization law; SCC required for EU transfers         | Railway EU West + SCC with Railway ✅     |

### Configuration Checklist

- [ ] Railway project region set to EU West (Amsterdam) before first deployment
- [ ] Cloudflare R2 bucket created in EU region (`EEUR` or `WEUR`)
- [ ] Sentry project set to EU data region (app.eu.sentry.io)
- [ ] All DATABASE_URL connection strings use Railway EU West endpoint

---

## Cross-Border Data Transfer Rules

### Transfer Mechanism Summary

| Data Flow                     | Transfer Type           | Legal Mechanism                         | Required Action                                       |
| ----------------------------- | ----------------------- | --------------------------------------- | ----------------------------------------------------- |
| HR users → Railway EU West    | EU → EU (intra-EEA)     | No mechanism needed                     | None                                                  |
| RS users → Railway EU West    | Third country → EU      | EU Adequacy Decision 2023/1485 (Serbia) | No additional contracts needed                        |
| BA users → Railway EU West    | Third country → EU      | **No adequacy decision for BiH**        | Standard Contractual Clauses (SCCs 2021/914) required |
| API → Sentry (error tracking) | EU → EU                 | Sentry EU region                        | Configure Sentry EU DSN                               |
| API → SEF portal (Serbia)     | EU host → RS gov portal | RS domestic processing                  | No GDPR concern (processed in RS by RS authority)     |
| API → FINA/HR-FISK (Croatia)  | EU → EU                 | EU to EU                                | No mechanism needed                                   |

### Standard Contractual Clauses — BiH Users

For BiH users whose data is stored on Railway (EU host):

1. **Module 2 SCCs** (Controller-to-Processor) required: Bilko as controller → Railway as processor
2. **Railway DPA** includes SCCs 2021/914 for non-EEA transfers
3. **Transfer Impact Assessment (TIA)** required before relying on SCCs:
   - Railway is US company but data stored in EU — assess EU GDPR applicability
   - Cloudflare processes BiH IP addresses at edge — assess data minimization
4. **Action required:** Sign Railway DPA with SCC addendum before accepting BiH customers

### Serbia Adequacy Decision

- **Decision:** European Commission Implementing Decision 2023/1485 of July 21, 2023
- **Effect:** Serbia treated as providing adequate protection equivalent to EU GDPR
- **Practical:** No SCCs, BCRs, or other transfer mechanisms needed for RS↔EU data flows
- **Caveat:** Adequacy decisions can be revoked — monitor European Commission communications

### BiH Adequacy Status

- **Current status:** BiH does NOT have EU adequacy decision (as of 2026)
- **Expected:** ZZLP reform expected ~2027 may trigger adequacy assessment
- **Action:** Track EDPB opinions and European Commission decisions for BiH

---

## Serbia — Zakon o računovodstvu (Accounting Law)

### Applicability

- **Applies to:** All legal entities in Serbia
- **Scope:** Financial record-keeping, reporting, retention

### Requirements

#### 1. Chart of Accounts

**Regulation:** Companies must use standardized chart of accounts (Kontni plan)

**Implementation:**

- Bilko allows custom chart of accounts
- Provide Serbian CoA template (predefined accounts)

**Status:** PLANNED — Create Serbian CoA seed data

---

#### 2. Double-Entry Bookkeeping

**Regulation:** All transactions must use double-entry (debit + credit)

**Implementation:**

- Prisma schema enforces double-entry (`debitAccountId` + `creditAccountId`)
- Backend validates debit = credit

**Status:** COMPLIANT (by design)

---

#### 3. Financial Reporting

**Required reports:**

- Bilans stanja (Balance Sheet)
- Bilans uspeha (Income Statement)
- Izvještaj o novčanim tokovima (Cash Flow Statement)

**Implementation:**

- Bilko generates P&L, Balance Sheet, Cash Flow
- Export to PDF (Serbian language support)

**Status:** PLANNED — Backend report generation

---

#### 4. Data Retention

**Regulation:** Financial records must be kept minimum 5 years

**Implementation:**

- Soft delete (never hard delete financial data)
- Backup retention: 30 days (Railway automatic backups)

**Status:** PLANNED

---

### SEF (Sistem E-Faktura) — Electronic Invoicing

**Requirement:** B2G (business-to-government) invoices must be submitted electronically via SEF portal.

**Applicability:**

- Mandatory for government contracts
- Optional for B2B (as of 2026)

**Implementation (Phase 2):**

- SEF XML export format
- API integration with SEF portal
- Digital signature (qualified certificate)

**Status:** NOT IMPLEMENTED — Deferred to Phase 2

---

## Bosnia & Herzegovina — Zakon o PDV-u (VAT Law)

### VAT Rates

- **Standard:** 17%
- **Reduced:** 0% (exports, specific goods)

### Requirements

#### 1. VAT Calculation

**Implementation:**

- Bilko supports configurable tax rates per invoice item
- Default tax rate: 17% for BiH organizations

**Status:** COMPLIANT (by design)

---

#### 2. VAT Reporting

**Required report:**

- PDV prijava (VAT return) — monthly or quarterly

**Implementation:**

- Bilko generates VAT report (sales, purchases, net VAT)
- Export to PDF

**Status:** PLANNED — Backend report generation

---

#### 3. Electronic Bookkeeping

**Regulation:** Companies with revenue >50,000 BAM must maintain electronic records.

**Implementation:**

- Bilko is cloud-based (electronic by default)
- Data export to XML (future integration with tax authority)

**Status:** PLANNED (Phase 2)

---

## Croatia — Zakon o fiskalizaciji (Fiscalization Law)

### Applicability

- **Applies to:** All businesses with cash transactions (retail, hospitality, services)

### Requirements

#### 1. Fiscalization (Fiskalizacija 2.0)

**Regulation:** All invoices must be registered with tax authority in real-time.

**Implementation (Phase 2):**

- API integration with Porezna uprava (tax authority)
- Digital signature (qualified certificate)
- Unique invoice identifier (JIR) from tax authority
- QR code on invoice (links to tax authority verification)

**Status:** NOT IMPLEMENTED — Deferred to Phase 2

---

#### 2. eRačun (Public Sector Invoicing)

**Requirement:** B2G invoices must be submitted via eRačun system.

**Implementation (Phase 2):**

- UBL XML format
- Integration with eRačun portal

**Status:** NOT IMPLEMENTED — Deferred to Phase 2

---

## Multi-Country Compliance Matrix

| Requirement                    | Serbia          | BiH             | Croatia     | Implementation Status        |
| ------------------------------ | --------------- | --------------- | ----------- | ---------------------------- |
| **Double-entry bookkeeping**   | ✅ Required     | ✅ Required     | ✅ Required | ✅ Compliant (Prisma schema) |
| **VAT calculation**            | 20%             | 17%             | 25%         | ✅ Compliant (configurable)  |
| **VAT reporting**              | ✅ Required     | ✅ Required     | ✅ Required | ⏳ Planned                   |
| **Financial reports**          | ✅ Required     | ✅ Required     | ✅ Required | ⏳ Planned                   |
| **Data retention (5 years)**   | ✅ Required     | ✅ Required     | ✅ Required | ⏳ Planned                   |
| **Electronic invoicing (B2G)** | ✅ SEF          | ❌ Optional     | ✅ eRačun   | ❌ Phase 2                   |
| **Real-time fiscalization**    | ❌ Not required | ❌ Not required | ✅ Required | ❌ Phase 2                   |
| **Digital signature**          | ❌ Not required | ❌ Not required | ✅ Required | ❌ Phase 2                   |

---

## Compliance Roadmap

### Phase 1 (MVP) — GDPR Only

- ✅ Privacy policy drafted
- ✅ Terms of Service drafted
- ✅ Data minimization (by design)
- ✅ Encryption (TLS + AES-256)
- ⏳ User data deletion workflow
- ⏳ Data export (JSON)
- ⏳ Sign DPAs with processors

**Timeline:** Pre-launch (before first customer)

---

### Phase 2 (Serbia Launch)

- ⏳ Serbian CoA template
- ⏳ VAT reporting (20%)
- ⏳ Financial reports (Balance Sheet, P&L, Cash Flow)
- ⏳ SEF integration (B2G invoicing)
- ⏳ Legal review by Serbian lawyer

**Timeline:** 3-6 months after MVP

---

### Phase 3 (Regional Expansion)

- ⏳ BiH VAT support (17%)
- ⏳ Croatian VAT support (25%)
- ⏳ Croatian fiscalization (real-time)
- ⏳ eRačun integration (Croatia)
- ⏳ Multi-language support (SR, BS, HR)

**Timeline:** 12-18 months after MVP

---

## Compliance Checklist (Pre-Launch)

### GDPR

- [ ] Privacy policy published
- [ ] Terms of Service published
- [ ] Cookie banner (if using cookies)
- [ ] User consent mechanism
- [ ] Data deletion workflow
- [ ] Data export endpoint
- [ ] DPAs signed (Railway, Vercel, Cloudflare, SendGrid)
- [ ] Railway EU region configured
- [ ] Breach notification process documented

### Serbia (Phase 2)

- [ ] Legal review (Serbian accounting law)
- [ ] Serbian CoA template
- [ ] VAT calculation (20%)
- [ ] Financial reports (Serbian format)
- [ ] SEF integration (optional for MVP)

### BiH (Phase 3)

- [ ] Legal review (BiH VAT law)
- [ ] VAT calculation (17%)
- [ ] PDV prijava report

### Croatia (Phase 3)

- [ ] Legal review (Croatian fiscalization law)
- [ ] VAT calculation (25%)
- [ ] Fiscalization integration (mandatory)
- [ ] Qualified digital certificate
- [ ] eRačun integration

---

## Risk Assessment

| Risk                               | Likelihood         | Impact      | Mitigation                                 |
| ---------------------------------- | ------------------ | ----------- | ------------------------------------------ |
| **GDPR fine**                      | Low (if compliant) | High (€20M) | Implement all GDPR requirements pre-launch |
| **Data breach**                    | Medium             | High        | Encryption, rate limiting, security audit  |
| **Serbian non-compliance**         | Medium             | Medium      | Hire local accountant as advisor           |
| **Croatian fiscalization failure** | Low (Phase 3)      | High        | Partner with Croatian accounting firm      |
| **User data loss**                 | Low                | High        | Daily backups, test restore process        |

---

## Legal Disclaimer

**IMPORTANT:** This document is for internal planning only. It is NOT legal advice.

**Before launch:**

- Consult GDPR lawyer (EU compliance)
- Consult Serbian lawyer (accounting law)
- Consult BiH/Croatian lawyers (Phase 2/3)
- Review Privacy Policy with lawyer
- Review Terms of Service with lawyer

**Recommended Lawyers:**

- GDPR: Find lawyer specialized in EU data protection
- Serbia: Find lawyer specialized in računovodstvo (accounting law)

---

## Related Documents

- Security Architecture: [SECURITY-ARCHITECTURE.md](SECURITY-ARCHITECTURE.md)
- Deployment Guide: [../infrastructure/DEPLOYMENT.md](../infrastructure/DEPLOYMENT.md)
- Privacy Policy: [../legal/PRIVACY-POLICY.md](../legal/PRIVACY-POLICY.md)
- Terms of Service: [../legal/TERMS-OF-SERVICE.md](../legal/TERMS-OF-SERVICE.md)

---

**Last Updated:** 2026-02-25
**Status:** Final
**Compliance Implementation:** In Progress — GDPR Phase 1 pre-launch; Balkan regulations Phase 2
**Next Review:** Before first paying customer
**Compliance Officer:** Deferred to Phase 2 — accounting advisor to be engaged before Serbia launch

# Breach Response Plan

# Data Breach Response Plan

> **Organization:** Bilko — Balkan Accounting SaaS
> **Document Number:** IRP-SEC-001
> **Version:** 1.0
> **Date:** 2026-02-23
> **Author:** DPO / CTO
> **Status:** Draft — requires DPO approval before launch
> **Reviewers:** CTO, DPO, CEO
> **Classification:** Confidential

## Document History

| Version | Date       | Author | Changes                                                        |
| ------- | ---------- | ------ | -------------------------------------------------------------- |
| 0.1     | 2026-02-23 | DPO    | Initial breach response plan — three-jurisdiction (RS, BA, HR) |

---

## 1. Incident Response Team

| Role               | Person   | Contact                 | Responsibility                                      |
| ------------------ | -------- | ----------------------- | --------------------------------------------------- |
| Incident Commander | CTO      | cto@bilko.io            | Technical response, containment, investigation      |
| DPO                | DPO      | dpo@bilko.io            | Regulatory notification, data subject communication |
| CEO                | CEO      | ceo@bilko.io            | Stakeholder comms, business decisions, media        |
| Legal Counsel      | External | legal@bilko.io          | Regulatory advice, notification drafting            |
| On-call Engineer   | Rotates  | Slack: #bilko-incidents | First responder — detection, initial containment    |

**Escalation order:** On-call → CTO → DPO → CEO

---

## 2. What Constitutes a Breach

A personal data breach is any security incident leading to accidental or unlawful destruction, loss, alteration, unauthorized disclosure of, or access to, personal data.

### Examples

| Incident                                              | Breach?                | Severity |
| ----------------------------------------------------- | ---------------------- | -------- |
| Unauthorized access to a customer's invoice data      | YES                    | HIGH     |
| Exposure of PIB/JMBG/OIB/JIB tax IDs                  | YES                    | CRITICAL |
| IBAN numbers exposed                                  | YES                    | CRITICAL |
| Railway DB credentials exposed                        | YES — potential breach | CRITICAL |
| Server error logs contain email addresses             | YES (minor)            | LOW      |
| Employee accidentally emails invoice to wrong address | YES                    | MEDIUM   |
| Unsuccessful SQL injection attempt (no data accessed) | NO — log and monitor   | LOW      |
| DDoS attack — service unavailable, no data accessed   | NO                     | N/A      |

---

## 3. Response Timeline

```mermaid
timeline
    title Breach Response Timeline (72 hours)
    section Hour 0
        Detection : Alert fires (monitoring, customer report, employee discovery)
        Verification : Is this a real breach? Contain false positives.
    section Hour 1-4
        Containment : Block attacker, revoke credentials, isolate affected systems
        Assessment : What data was accessed? How many records? Which jurisdictions?
    section Hour 4-24
        Investigation : Root cause analysis. Audit logs (LoggedAction table).
        Internal notification : CTO, DPO, CEO briefed.
    section Hour 24-48
        Regulatory notification : Poverenik (RS), AZLP (BA), AZOP (HR) if required
        Evidence preservation : Immutable audit trail extracted. Logs archived.
    section Hour 48-72
        Data subject notification : If high risk to individuals
        Remediation : Patch deployed. Controls improved.
    section After 72h
        Post-mortem : Root cause documented. Prevention measures implemented.
        Follow-up reporting : Supervisory authorities updated if required.
```

---

## 4. Regulatory Notification Requirements

### 4.1 Notification Thresholds

| Jurisdiction         | Law              | Notify Authority         | Deadline                                       | Condition                                                      |
| -------------------- | ---------------- | ------------------------ | ---------------------------------------------- | -------------------------------------------------------------- |
| Croatia              | GDPR Art. 33     | AZOP (azop.hr)           | **72 hours**                                   | Unless breach is unlikely to result in risk to rights/freedoms |
| Serbia               | ZZPL Art. 56     | Poverenik (poverenik.rs) | **72 hours**                                   | Applies analogously (GDPR-aligned law)                         |
| Bosnia & Herzegovina | ZZLP BiH Art. 20 | AZLP (azlp.gov.ba)       | **Best practice 72 hours** (law less specific) | Recommended to align with GDPR practice                        |

**Default:** Notify all three authorities unless legal counsel advises otherwise.

### 4.2 Authority Contact Details

| Authority                                   | Jurisdiction         | Website      | Notification Method                         |
| ------------------------------------------- | -------------------- | ------------ | ------------------------------------------- |
| Agencija za zaštitu osobnih podataka (AZOP) | Croatia              | azop.hr      | Online form + email: azop@azop.hr           |
| Poverenik za informacije od javnog značaja  | Serbia               | poverenik.rs | Online form at poverenik.rs/zastitapodataka |
| Agencija za zaštitu ličnih podataka (AZLP)  | Bosnia & Herzegovina | azlp.gov.ba  | Email: azlp@azlp.gov.ba                     |

### 4.3 Notification Content (per GDPR Art. 33 / ZZPL Art. 56)

Required information for supervisory authority notification:

```
1. Nature of the breach (what happened, how discovered)
2. Categories and approximate number of data subjects affected
3. Categories and approximate number of records affected
4. Contact details of DPO: dpo@bilko.io
5. Likely consequences of the breach
6. Measures taken or proposed to address the breach
```

**Template:** See Section 7.1

### 4.4 Data Subject Notification (GDPR Art. 34)

Notify affected individuals "without undue delay" if breach is **likely to result in high risk** to their rights and freedoms.

**High risk triggers for Bilko data:**

- Tax ID (PIB/JMBG/OIB/JIB) exposure — identity theft risk
- IBAN exposure — financial fraud risk
- Full invoice data exposure — business espionage risk

---

## 5. Response Procedures

### 5.1 Detection & Verification (0–1 hour)

**Detection sources:**

- Sentry error tracking — unusual error patterns
- Railway logs — unexpected query volumes or failed auth attempts
- Cloudflare WAF alerts — unusual traffic patterns
- Customer complaint — "I can see another company's data"
- Employee discovery

**Verification steps:**

1. Access Railway logs and Cloudflare analytics
2. Query LoggedAction table for anomalous access patterns:
   ```sql
   SELECT userId, orgId, action, tableName, ipAddress, COUNT(*)
   FROM "LoggedAction"
   WHERE timestamp > NOW() - INTERVAL '1 hour'
   GROUP BY userId, orgId, action, tableName, ipAddress
   ORDER BY COUNT(*) DESC;
   ```
3. Confirm whether actual personal data was accessed (not just attempted)
4. Declare incident: `#bilko-incidents` Slack channel + page Incident Commander

### 5.2 Containment (1–4 hours)

**Immediate actions (within 30 minutes of confirmation):**

- [ ] Revoke affected user sessions (invalidate JWT refresh tokens in DB)
- [ ] If credentials compromised: rotate all Railway environment secrets
- [ ] If SQL injection: enable Railway maintenance mode temporarily
- [ ] If insider threat: suspend user account, preserve audit logs
- [ ] If third-party compromise: revoke API keys to SEF/FINA/Sentry

**Preserve evidence:**

- Extract relevant LoggedAction rows to immutable storage (S3 / local encrypted archive) before any system changes
- Do not delete logs, rotate secrets in place (old secret documented in Vaultwarden with timestamp)

### 5.3 Assessment (4–24 hours)

Determine scope:

- Which organizations were affected?
- Which data categories? (check against Data Inventory in DPIA)
- Approximate number of data subjects?
- Which jurisdictions? (RS, BA, HR, or all?)
- Was data exfiltrated, or only accessed?
- What is the risk to data subjects?

**Severity classification:**

| Severity | Criteria                                          | Response                                                             |
| -------- | ------------------------------------------------- | -------------------------------------------------------------------- |
| CRITICAL | Tax IDs, IBAN, financial amounts exfiltrated      | Notify all authorities within 24h; notify all affected data subjects |
| HIGH     | Invoice metadata accessed across tenant boundary  | Notify authorities within 72h; assess individual notification        |
| MEDIUM   | Email/name exposure, no financial data            | Notify authorities if >250 records; assess individual notification   |
| LOW      | Single record, no sensitive data, no exfiltration | Document internally; no mandatory notification                       |

### 5.4 Regulatory Notification (24–72 hours)

1. DPO drafts notification using template in Section 7.1
2. Legal counsel reviews
3. CEO approves
4. DPO submits to AZOP (HR), Poverenik (RS), AZLP (BA) simultaneously
5. Log submission timestamp and reference numbers received

**If full details not available within 72 hours:** Submit initial notification with known information and state investigation is ongoing. Supplement with additional notifications as information becomes available (GDPR Art. 33(4) allows phased notification).

### 5.5 Data Subject Notification

If high risk determined (Section 4.4):

1. Identify email addresses of all affected data subjects
2. DPO drafts data subject notification (see Section 7.2)
3. Send via Bilko email account — do not use marketing email tools
4. Provide clear guidance on what to do (change password, monitor bank statements)

---

## 6. Post-Incident

### 6.1 Post-Mortem

Complete within 2 weeks of incident resolution. Template: `OPERATIONS/post-mortem.md`

- Root cause (5 Whys)
- Timeline reconstruction from LoggedAction + logs
- What controls failed?
- What controls worked?
- Action items with owners and deadlines

### 6.2 Regulatory Follow-Up

- AZOP, Poverenik, AZLP may request follow-up information within 3 months
- Maintain incident dossier for minimum 3 years (GDPR Art. 33(5))
- Document: what happened, who was notified, when, remediation taken

### 6.3 Insurance

- Notify cyber insurance provider if breach exceeds threshold (per policy)
- Preserve evidence for potential claims

---

## 7. Notification Templates

### 7.1 Supervisory Authority Notification (English — adapt per jurisdiction)

```
Subject: Personal Data Breach Notification — Bilko Cloud Accounting — [DATE]

To: [AZOP / Poverenik / AZLP]

We are reporting a personal data breach pursuant to [GDPR Art. 33 / ZZPL Art. 56 / ZZLP BiH Art. 20].

Controller: Bilko d.o.o. | dpo@bilko.io | +[phone]
DPO Contact: dpo@bilko.io

1. NATURE OF BREACH
[Description: what happened, when discovered, how]

2. DATA SUBJECTS AFFECTED
Approximate number: [NUMBER]
Categories: [accountants / business owners / invoice recipients]

3. RECORDS AFFECTED
Categories: [tax IDs / IBAN / invoice amounts / email addresses]
Approximate number: [NUMBER]

4. LIKELY CONSEQUENCES
[Identity theft risk / financial fraud risk / business espionage risk]

5. MEASURES TAKEN
[Containment steps, credential rotation, patch deployed]
[Ongoing investigation]

6. FURTHER INFORMATION
This notification is [complete / preliminary — further information to follow].

[DPO Name]
DPO — Bilko
dpo@bilko.io
```

### 7.2 Data Subject Notification (Croatian — adapt for RS/BA)

```
Predmet: Obavijest o povredi osobnih podataka — Bilko

Poštovani/a,

Obavještavamo Vas da je Bilko bio izložen sigurnosnom incidentu koji je mogao
utjecati na Vaše osobne podatke.

Što se dogodilo:
[Jednostavan opis — kada, što je bilo pristupljeno]

Koji su Vaši podaci bili zahvaćeni:
[Navesti konkretno: PIB, IBAN, iznosi računa — samo što je relevantno]

Što smo poduzeli:
[Koraci: blokiranje pristupa, obavještavanje AZOP-a, poboljšanje sigurnosti]

Što možete učiniti:
- Promijenite lozinku na bilko.io
- Pratite aktivnosti na bankovnim računima
- Kontaktirajte nas na dpo@bilko.io s pitanjima

Izvinjenje:
Žao nam je što se ovo dogodilo. Zaštita Vaših podataka naša je prioritetna obveza.

S poštovanjem,
Bilko tim
dpo@bilko.io
```

---

## Approval

| Role                   | Name      | Signature | Date       |
| ---------------------- | --------- | --------- | ---------- |
| Author                 | DPO / CTO |           | 2026-02-23 |
| Reviewer (CEO)         |           |           |            |
| DPO Approval           |           |           |            |
| Legal Counsel Approval |           |           |            |

# Bilko Rate Limiting — Trusted Client IP Strategy (ADR-022)

# Bilko Rate Limiting — Trusted Client IP Strategy (ADR-022)

## Summary

 This document describes the fix for the `X-Forwarded-For` spoofing vulnerability in Bilko's rate limiter (MC #99917, PR #63). The rate limiter previously read the leftmost (attacker-controlled) value from the `X-Forwarded-For` header using `firstOrNull()`, allowing trivial bypass of all three rate-limit buckets. The fix introduces a `TrustedIpExtractor` helper that reads a configurable number of trusted proxy hops from the right of the XFF chain via the `TRUSTED_PROXY_HOP_COUNT` environment variable (default: 2).

 Bilko's production topology (verified 2026-05-08) consists of three hops: **Internet → GCLB EXTERNAL\_MANAGED → Serverless NEG → Cloud Run GFE → Ktor**. GCLB appends the real client IP, and Cloud Run GFE appends the GCLB POP address, resulting in an XFF chain of `[attacker-supplied-values, real-client-ip, gclb-pop-ip]`. With `hopCount=2`, the extractor correctly reads `xff[size - 2]` to retrieve the real client IP.

## Network Topology

 Bilko's production topology (as of 2026-05-08) consists of:

```

Internet client
     |
     v
GCLB EXTERNAL_MANAGED (google_compute_global_forwarding_rule "bilko-stage-https-fwd")
     | TLS termination, Cloud Armor policy (if enabled)
     v
Serverless NEG (google_compute_region_network_endpoint_group "bilko-stage-api-neg")
     |
     v
Cloud Run GFE (internal Google Frontend — *.run.app / *.europe-north1.run.app)
     |
     v
Ktor Application (bilko-api-stage container)
```

 **X-Forwarded-For structure on the GCLB path:**

```

X-Forwarded-For: <attacker-supplied-values>, <real-client-ip>, <gclb-pop-ip>
                  ^                            ^                    ^
                  Index 0 (NEVER trust)        Index size-2        Index size-1
                                               (real client)       (GCLB POP, appended by GFE)
```

 **Direct \*.run.app bypass path:** With `INGRESS_TRAFFIC_ALL` set (confirmed live 2026-05-08), direct `*.run.app` requests skip GCLB entirely. On that path, only one GCP hop is appended (Cloud Run GFE), so `hopCount=2` would under-extract. This is a residual risk tracked in MC #99924 (FlowForge INGRESS lockdown).

 **GCP Documentation Reference:** [ https://cloud.google.com/load-balancing/docs/https#x-forwarded-for\_header ](https://cloud.google.com/load-balancing/docs/https#x-forwarded-for_header)

## TrustedIpExtractor Pattern

 The `TrustedIpExtractor` utility (`apps/api/src/main/kotlin/no/alai/bilko/util/TrustedIpExtractor.kt`) implements the following algorithm:

1. Read `TRUSTED_PROXY_HOP_COUNT` from the environment variable (default: 2).
2. Split the `X-Forwarded-For` header on commas, trim each entry, and filter out empty values.
3. Return the entry at index `size - hopCount`.
4. If the XFF header is absent or shorter than `hopCount`, fall back to `call.request.local.remoteAddress`.

### Code Excerpt

```

object TrustedIpExtractor {
    val hopCount: Int = run {
        val raw = System.getenv("TRUSTED_PROXY_HOP_COUNT")
        raw?.toIntOrNull()?.takeIf { it >= 1 } ?: 2
    }

    fun extractTrustedClientIp(call: ApplicationCall): String {
        val xffHeader = call.request.header("X-Forwarded-For")
        val remoteAddress = call.request.local.remoteAddress
        return extractFromParts(xffHeader, remoteAddress, hopCount)
    }

    fun extractFromParts(xffHeader: String?, remoteAddress: String, hopCount: Int = this.hopCount): String {
        if (xffHeader.isNullOrBlank()) return remoteAddress

        val parts = xffHeader.split(",").map { it.trim() }.filter { it.isNotEmpty() }

        if (parts.size < hopCount) {
            return remoteAddress  // XFF chain shorter than expected — fall back
        }

        return parts[parts.size - hopCount]
    }
}
```

### Fallback Behavior

 **WARNING:** On Cloud Run, `call.request.local.remoteAddress` is the Cloud Run GFE internal network address — **NOT** the real client IP. Falling back here degrades the rate-limiter to per-GFE-region keying (all requests without XFF share one bucket per region). This is acceptable as a last resort; it is not a security bypass.

### Environment Variable Contract

- **Name:** `TRUSTED_PROXY_HOP_COUNT`
- **Default:** 2 (correct for Bilko GCLB + Cloud Run GFE topology)
- **Valid range:** ≥ 1 (negative or zero values fall back to default)
- **Override location:** `apps/api/src/main/resources/.env.example`

## Rate Limiting Bucket Strategy (Post #99917)

 Bilko uses three rate-limit buckets, each with distinct keying strategies:

### 1. `auth` Bucket (5 requests/minute)

 **Applied to:** Pre-authentication routes (`/register`, `/login`, `/2fa/challenge`, `/refresh`).

 **Key strategy:** IP address via `TrustedIpExtractor` (closes XFF spoofing).

 **Trade-off:** A shared corporate NAT means all users behind it share the same rate-limit bucket. This is acceptable for login attempts (5 attempts/min × team size is typically sufficient). JWT principal is not yet issued at this stage, so IP keying is the only viable option.

 **Alternative considered:** Email/username keying from request body (Parisa dissent PARISA-TABRIZ-D1) — deferred as follow-up improvement.

### 2. `api` Bucket (100 requests/minute)

 **Applied to:** All authenticated routes inside `authenticate("bilko-jwt")`.

 **Key strategy:** JWT principal `organizationId` from `BilkoPrincipal`. Falls back to IP via `TrustedIpExtractor` if principal is unavailable (should not normally happen inside the authenticated block).

 **Benefit:** Removes office-NAT lockout for paying customers. Each organization has its own rate-limit bucket.

### 3. `public` Bucket — REMOVED

 The `public` bucket was registered in `RateLimit.kt` but never mounted in `Routing.kt` (confirmed: no route file uses the public bucket name). Dead code removed to eliminate confusion. If a public route is added in future, add the bucket registration back alongside the matching `rateLimit(...) { ... }` route wrapper.

## ADR-022 — IP Trust Strategy

### Status

**Accepted** (2026-05-08)

### Context

 The rate limiter in `RateLimit.kt` previously used `X-Forwarded-For.split(",").firstOrNull()` to extract the client IP, reading the leftmost (attacker-controlled) value. Any caller who could set HTTP headers could bypass all three rate-limit buckets by supplying a fresh fake IP per request.

 Bilko's production topology consists of three hops: GCLB EXTERNAL\_MANAGED + Serverless NEG + Cloud Run GFE. GCLB preserves the incoming XFF and appends the real client IP; Cloud Run GFE then appends the GCLB POP address. The real client IP is therefore at index `size - 2` (two trusted hops from the right).

### Decision

 We will use an explicit **trusted proxy hop count** via the `TRUSTED_PROXY_HOP_COUNT` environment variable to determine the correct extraction offset. The default value is **2** for Bilko's current GCLB + Cloud Run topology. This makes the offset empirically verifiable and reconfigurable without requiring a code deploy.

### Consequences

- **Positive:** Topology changes (e.g., adding a Cloudflare proxy layer upstream) require only an environment variable update, not a code change. The extraction logic is testable and deterministic.
- **Negative:** Direct `*.run.app` bypass remains open (tracked in MC #99924) until `INGRESS_TRAFFIC_ALL` is locked to `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER_ONLY`. On that path, `hopCount=2` under-extracts by one position (Cloud Run GFE appends only one hop, not two).
- **Deferred:** Distributed rate-limit state (Jedis) is tracked as a follow-up MC. In-memory per-instance counters mean `auth` bucket (limit=5) multiplies by instance count. With `max_instance_count=2`, effective limit is 10 brute-force attempts per minute. Jedis is declared in `build.gradle.kts` but not wired.
- **Parallel vulnerability:** `CallLogging.kt` lines 31-32 log the raw full XFF string without trusted-IP extraction. Fabricated IPs persist in the audit log. Tracked in MC #99925 (same fix pattern: use `TrustedIpExtractor`).

### Alternatives Considered

1. **Naive `lastOrNull()`** (Kelsey dissent): Wrong for Bilko's 3-hop GCLB topology. `lastOrNull()` consumes the GCLB POP IP, not the real client IP. Correct index is `size - 2`.
2. **Cloud Armor IP rate-limiting at GFE layer** (Petter dissent): Deferred. Compute Engine API is disabled on project `tribal-sign-487920-k0`. Cloud Armor is declared in Terraform (`enable_cloud_armor = true`) but not deployed. Tracked as a follow-up MC (FlowForge infra workstream).
3. **Jedis distributed counter** (Kleppmann dissent): Deferred. In-memory per-instance counters are insufficient for the `auth` bucket at scale, but implementing Jedis-backed distributed rate-limiting is scope-creep for this MC. Tracked as a follow-up MC after Redis endpoint is provisioned.
4. **Identity-based keying (email/username) for auth bucket** (Parisa dissent): Rejected for this MC. JWT principal is not yet issued at pre-auth stage, so email/username extraction from request body is the only alternative to IP keying. This requires route-level key function (not plugin-level) and is deferred as a follow-up improvement.

## Operational Notes

### Override TRUSTED\_PROXY\_HOP\_COUNT

 If the network topology changes (e.g., adding a Cloudflare proxy layer), update `TRUSTED_PROXY_HOP_COUNT` in `apps/api/src/main/resources/.env.example` and redeploy. For example, adding Cloudflare upstream would require `TRUSTED_PROXY_HOP_COUNT=3`.

### Live Spoof Probe (Post-Merge)

 After PR #63 is merged and deployed to stage, run the following probe to verify the fix:

```

for i in {1..6}; do
  curl -H "X-Forwarded-For: 1.2.3.4" -i https://api.bilko.io/api/v1/auth/login
done
```

 **Expected result:** First 4 requests succeed (200 OK). Requests 5 and 6 return `429 Too Many Requests` keyed on the **real client IP**, not the spoofed `1.2.3.4` value.

### Known Limitation: \*.run.app Direct Bypass

 As of 2026-05-08, both `bilko-api` and `bilko-api-stage` use `ingress = "INGRESS_TRAFFIC_ALL"` (verified in `compute/main.tf` line 28). This means direct `*.run.app` URL access bypasses GCLB and Cloud Armor. XFF on that path is 100% attacker-controlled regardless of any IP extraction logic in `RateLimit.kt`.

 **Mitigation:** MC #99924 (FlowForge, H priority) will lock ingress to `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER_ONLY`. This fix (#99917) mitigates the GCLB path only.

 **Impact analysis:** `CORS_ORIGINS` env variable (section 5 of topology probe) confirms `*.run.app` URLs are present in CORS allow-list. Audit required to determine if these are stale entries or if web frontend still calls the `*.run.app` URL directly.

## Cross-References

- **PR #63:** [feat/99917-trusted-ip-extractor](https://github.com/johnatbasicas/bilko/pull/63)
- **MC #99917:** This fix (rate-limit IP trust strategy)
- **MC #99924:** INGRESS lockdown (FlowForge, H priority, parallel)
- **MC #99925:** CallLogging.kt parallel vulnerability (CodeCraft, M priority, depends on #99917)
- **Forged prompt:** `~/system/prompts/forged/99917.md`
- **Topology probe:** `docs/security/rate-limit-topology-probe-2026-05-08.md`

---

  ***Document prepared by:** Skillforge (MC #99917 D5)  
 **Validated by:** Proveo (angie-jones, MC #99917 D4)  
 **Date:** 2026-05-08  
 **Status:** ADR-022 Accepted*