Skip to main content

Security Testing Policy

Security Testing Policy

Organization: Bilko — Balkan Accounting SaaS Policy Number: POL-SEC-TEST-001 Version: 1.0 Date: 2026-02-23 Author: Compliance Architect Status: Draft Reviewers: CTO, 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 security testing policy

1. Purpose & Scope

Purpose: This policy defines security testing methodology, tools, frequency, and remediation requirements for all systems operated by Bilko. Security testing is mandatory — no system goes to production without completing applicable security tests.

Scope:

  • All production applications: bilko.io (Vercel), api.bilko.io (Railway)
  • PostgreSQL database (Railway EU West)
  • Cloudflare R2 file storage
  • All CI/CD pipelines
  • Third-party integrations: SEF (Serbia), HR-FISK/FINA (Croatia)

Policy Owner: Compliance Architect ([email protected])

Regulatory basis:

  • GDPR Art. 32 — "regular testing, assessing and evaluating the effectiveness of technical and organisational measures"
  • ZZPL Art. 50 (Serbia) — security testing obligation
  • Zakon o računovodstvu — integrity of financial processing systems

2. Security Testing Methodology

Approach: Shift-left security — testing integrated throughout development, not bolted on at the end.

Development Phase
    ├── SAST — Static code analysis (every commit)
    ├── SCA — Dependency vulnerability scan (every commit)
    └── Secret scanning — Detect leaked credentials (every commit)

Build / CI Phase
    ├── SAST — Full codebase scan (every PR)
    ├── SCA — Full dependency audit (every PR)
    └── Secret scanning — Full repo scan (every PR)

Deployment Phase
    ├── DAST — OWASP ZAP scan against staging (every deployment)
    └── API security: Org-scope isolation tests (every deployment)

Operational Phase
    ├── Vulnerability assessment — External attack surface (quarterly)
    └── Penetration test — Manual expert testing (annual, Phase 2+)

3. Testing Types, Tools & Schedule

3.1 SAST — Static Application Security Testing

Purpose: Detect security vulnerabilities in TypeScript/JavaScript source code.

Property Value
Tool ESLint with eslint-plugin-security + TypeScript strict mode
Frequency Every commit (pre-commit hook) + every PR (CI)
Languages TypeScript (frontend Next.js + backend Express)
Integration Pre-commit hook + GitHub Actions
Blocking YES — PR cannot merge if Critical or High findings

SAST rules in scope:

  • OWASP Top 10 (A01-A10)
  • SQL injection patterns
  • XSS patterns (dangerouslySetInnerHTML without sanitization)
  • Hardcoded secrets / API keys
  • Insecure JWT usage (no algorithm verification, expired tokens not rejected)
  • Raw SQL in Prisma ($queryRaw with user input)
  • Missing org-scope WHERE clauses
  • No-eval, no-unsafe-regex rules

ESLint configuration:

{
  "extends": ["plugin:security/recommended"],
  "rules": {
    "security/detect-object-injection": "warn",
    "security/detect-non-literal-regexp": "error",
    "security/detect-possible-timing-attacks": "error",
    "no-eval": "error",
    "@typescript-eslint/no-explicit-any": "error"
  }
}

3.2 DAST — Dynamic Application Security Testing

Purpose: Detect runtime vulnerabilities by sending attack traffic to a running instance.

Property Value
Tool OWASP ZAP (Zed Attack Proxy)
Frequency Every deployment to staging + weekly full scan
Target https://staging.bilko.io — NEVER production without written approval
Integration Post-deployment CI step
Blocking YES — deployment halted if Critical finding

DAST scan scope for Bilko:

  • All API endpoints (authenticated + unauthenticated)
  • Authentication flows: login, register, password reset, 2FA
  • Invoice CRUD endpoints (create, update, delete)
  • Expense endpoints
  • VAT report generation
  • File upload (receipt attachments)
  • SEF/HR-FISK submission endpoints
  • Org-scope isolation (cross-tenant access attempts)
  • Security headers presence and correctness
  • SSL/TLS configuration

Critical Bilko-specific DAST tests:

1. Cross-tenant IDOR test:
   - Login as org A user
   - Attempt GET /api/v1/invoices/{invoice-id-from-org-B}
   - Expected: 403 Forbidden
   - If 200: CRITICAL finding — immediate halt

2. RBAC bypass test:
   - Login as accountant role
   - Attempt POST /api/v1/invoices (create)
   - Expected: 403 Forbidden
   - Attempt DELETE /api/v1/invoices/{id}
   - Expected: 403 Forbidden

3. SQL injection via Prisma:
   - Attempt org-scope bypass via invoice filter parameters
   - Expected: Prisma parameterization prevents injection

4. Tax ID in response check:
   - Verify tax IDs returned as masked/encrypted, not plaintext
   - Verify tax IDs not present in error responses or logs

3.3 SCA — Software Composition Analysis

Purpose: Identify known vulnerabilities in open source dependencies.

Property Value
Tool npm audit + Dependabot (GitHub)
Frequency Every commit (Dependabot) + weekly full npm audit
Blocking YES — PR cannot merge with Critical CVE (unresolved)
License check YES — copyleft licenses require legal review

Dependency update policy:

  • Security patches (Critical/High CVE): Merge within 24h / 7 days per remediation SLA
  • Minor updates: Merge within 14 days
  • Major updates: Planned migration within 90 days

Approved licenses: MIT, Apache 2.0, BSD (2-clause, 3-clause), ISC Requires review: GPL, LGPL, AGPL

Current Bilko key dependencies to monitor:

prisma (ORM — SQL injection prevention)
jsonwebtoken / jose (JWT — auth bypass if vulnerable)
bcrypt / bcryptjs (password hashing — compromise risk)
zod (input validation — bypass risk)
express (web framework — RCE risk)

3.4 Vitest Security Tests

Purpose: Bilko-specific automated security regression tests for accounting isolation.

Property Value
Tool Vitest (unit + integration) + Playwright (E2E)
Frequency Every commit (CI)
Blocking YES — test failure blocks deployment

Required security test cases:

// 1. RBAC tests — all role/action combinations
describe('RBAC', () => {
  test('accountant cannot create invoice', async () => {
    const res = await api.post('/api/v1/invoices', invoice, { role: 'accountant' });
    expect(res.status).toBe(403);
  });
  test('viewer cannot generate report', async () => {
    const res = await api.get('/api/v1/reports/vat', { role: 'viewer' });
    expect(res.status).toBe(403);
  });
});

// 2. Org-scope isolation tests
describe('Multi-tenant isolation', () => {
  test('org A cannot access org B invoice', async () => {
    const orgBInvoice = await createInvoice({ org: 'org-b' });
    const res = await api.get(`/api/v1/invoices/${orgBInvoice.id}`, { org: 'org-a' });
    expect(res.status).toBe(403);
  });
  test('org A cannot list org B contacts', async () => {
    const res = await api.get('/api/v1/contacts', { org: 'org-a' });
    expect(res.body.data.every(c => c.organizationId === 'org-a-id')).toBe(true);
  });
});

// 3. VAT calculation accuracy tests
describe('VAT calculations', () => {
  test('Serbian VAT at 20%', () => {
    expect(calculateVAT(new Decimal('100.00'), 'RS')).toEqual(new Decimal('20.00'));
  });
  test('Croatian VAT at 25%', () => {
    expect(calculateVAT(new Decimal('100.00'), 'HR')).toEqual(new Decimal('25.00'));
  });
  test('BiH VAT at 17%', () => {
    expect(calculateVAT(new Decimal('100.00'), 'BA')).toEqual(new Decimal('17.00'));
  });
  test('NUMERIC precision — no float rounding', () => {
    expect(calculateVAT(new Decimal('33.33'), 'RS')).toEqual(new Decimal('6.67'));
  });
});

// 4. Input validation tests
describe('Zod validation', () => {
  test('rejects non-UUID customerId', async () => {
    const res = await api.post('/api/v1/invoices', { customerId: 'not-a-uuid' });
    expect(res.status).toBe(400);
  });
  test('rejects future invoice date beyond 1 year', async () => {
    const res = await api.post('/api/v1/invoices', { invoiceDate: '2030-01-01' });
    expect(res.status).toBe(400);
  });
});

3.5 Penetration Testing

Purpose: Expert manual security assessment.

Property Value
Frequency Annual minimum; after major architectural changes
When Before Croatia/Serbia launch (mandatory for HR-FISK compliance)
Provider External security firm (TBD)
Methodology OWASP Testing Guide v4.2 + PTES
Blocking Critical findings: system taken offline until remediated

Penetration test scope for Bilko:

In-scope:
- Production: https://bilko.io, https://api.bilko.io
- Authentication flows (login, register, 2FA, password reset)
- All authenticated API endpoints
- Multi-tenant isolation (IDOR across organizations)
- File upload (receipt attachments)
- SEF and HR-FISK submission endpoints
- Financial calculation integrity (VAT, double-entry)

Out-of-scope:
- Denial of service attacks
- Railway and Vercel infrastructure (cloud provider responsibility)
- Cloudflare infrastructure
- Social engineering
- Third-party SEF/FINA systems

Rules of engagement:
- Testing window: Agreed in writing before engagement
- Halt on critical finding: Immediately notify [email protected]
- Test accounts provided: Separate test organizations in staging

4. Vulnerability Classification & Remediation SLAs

4.1 Severity Classification

Severity CVSS Definition Bilko Example
Critical 9.0-10.0 Remote code execution, full auth bypass, mass data access without auth Prisma raw SQL injection exposing all orgs
High 7.0-8.9 Privilege escalation, cross-tenant IDOR, authenticated auth bypass Org-scope bypass — viewing another org's invoices
Medium 4.0-6.9 Limited data exposure, CSRF, RBAC bypass for non-critical action Viewer role accessing reports
Low 0.1-3.9 Minor info disclosure, theoretical risk Stack trace in error response
Informational N/A Best practice recommendation Missing cache-control header

4.2 Remediation SLAs

Severity SLA Action on Breach
Critical Immediate containment 4h; full remediation 24 hours System taken offline if risk cannot be mitigated; notify CEO
High 7 calendar days Escalate to CTO + CEO. Engineering manager approves extension.
Medium 30 calendar days Engineering lead tracks.
Low 90 calendar days Backlog. Quarterly security review.
Informational Next sprint (best effort) Not SLA-bound.

Cross-tenant IDOR (High+) is treated as Critical for Bilko — financial data exposure for any organization triggers immediate P1 incident response.


5. Security Code Review Checklist

Required for every PR touching authentication, RBAC, financial calculations, or PII:

Authentication & Authorization:

  • No hardcoded secrets in code
  • JWT validated: signature, expiry, iss, aud
  • org-scoped WHERE clause on every Prisma query touching multi-tenant data
  • RBAC requireRole() middleware applied on every protected endpoint
  • 2FA code validation uses constant-time comparison

Input Validation:

  • Zod schema for all request bodies
  • Prisma queries only — no $queryRaw with user input
  • File uploads: type validated, stored in R2 not webroot
  • No string interpolation in SQL or system commands

Financial Integrity:

  • All monetary values use Decimal / NUMERIC — no number or float
  • VAT rates validated against country code (not user-supplied)
  • Double-entry: debit = credit enforced
  • Exchange rates locked at transaction date

Cryptography:

  • No MD5, SHA-1, DES, RC4 anywhere
  • Random values from crypto.randomBytes() — not Math.random()
  • bcrypt cost factor = 12 for any new password hashing
  • Private keys / secrets not in source code or logs

Data Handling:

  • Tax IDs (PIB/JMBG/OIB/JIB) and IBAN encrypted before storage
  • Sensitive data not in query parameters or URLs
  • PII not in Sentry error reports (use .addEventProcessor() to scrub)
  • LoggedAction entries for all financial mutations

6. Security Testing in CI/CD Pipeline

flowchart LR
    COMMIT[Developer\nCommit] -->|Pre-commit| SAST_INC[ESLint\nSAST incremental]
    SAST_INC -->|Pass| PUSH[Git Push]
    PUSH --> PR[Pull Request]

    PR --> SAST_FULL[ESLint Full\n+ TypeScript strict]
    PR --> SCA[npm audit\nDependabot]
    PR --> SECRET[Secret\nScanning]
    PR --> VITEST[Vitest\nSecurity tests]

    SAST_FULL & SCA & SECRET & VITEST -->|All pass| BUILD[Build]
    BUILD --> STAGING[Deploy Staging]
    STAGING --> DAST[OWASP ZAP\nDAST scan]
    STAGING --> E2E[Playwright\nIsolation tests]
    DAST & E2E -->|Pass| GATE{Security Gate}

    GATE -->|Pass| PROD[Deploy Production]
    GATE -->|Fail| BLOCK[Block Deploy\nAlert [email protected]]

Pipeline security gate criteria:

Gate Tool Blocking Criteria
Pre-commit ESLint security rules Critical findings in changed files
PR gate 1 ESLint + TypeScript strict Any Critical or High
PR gate 2 npm audit Critical CVE
PR gate 3 Secret scanning Any detected secret
PR gate 4 Vitest security tests Any test failure
Post-staging OWASP ZAP Critical or High dynamic finding
Post-staging Playwright E2E isolation Cross-tenant access succeeds (should fail)

7. Metrics & KPIs

Metric Target Frequency
Critical findings resolved within SLA 100% Monthly
High findings resolved within SLA 95% Monthly
MTTR — Critical < 24 hours Per incident
MTTR — High < 7 days Monthly
% deployments with passing security gate 100% Monthly
Vitest security test pass rate 100% Every deployment
Open High+ findings (backlog) 0 Monthly
npm audit — Critical CVEs 0 Weekly
Penetration test — Critical findings closed 100% within 30 days Per test

Security metrics tracking: GitHub Issues with security label Quarterly security report: Published to CEO and engineering team


Approval

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