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 (
$queryRawwith 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
$queryRawwith 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— nonumberorfloat - 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()— notMath.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 |