Security Testing Policy
Security Testing Policy
Organization:
{{ORG_NAME}}Bilko — Balkan Accounting SaaS Policy Number: POL-SEC-TEST-{{NUMBER}}001 Version:{{VERSION}}1.0 Date:{{DATE}}2026-02-23 Author:{{AUTHOR}}Compliance Architect Status: Draft| In Review | ApprovedReviewers:{{REVIEWERS}}CTO, Engineering Lead Next Review:{{REVIEW_DATE}}2026-08-23 Classification: Confidential
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | Initial draft — Bilko security testing policy |
1. Purpose & Scope
Purpose: This policy defines the security testing methodology, tools, frequency, and remediation requirements for all systems operated by {{ORG_NAME}}.Bilko. Security testing is mandatory and non-negotiable — no system goes to production without completing applicable security tests.
Scope:
- All production
applicationsapplications:andbilko.ioAPIs(Vercel), api.bilko.io (Railway) AllPostgreSQLinfrastructuredatabase (cloud,Railwayon-premises,EUcontainers)West)AllCloudflareinternalR2toolsfilewith access to production datastorage- All CI/CD pipelines
and build systems All third-Third-partyintegrationsintegrations:whereSEF{{ORG_NAME}}(Serbia),controlsHR-FISK/FINAthe integration code(Croatia)
Policy Owner: {{CISO_NAME}}Compliance Architect ({{CISO_EMAIL}})[email protected])
OperationalRegulatory Owner: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.
Testing layers:
Development Phase
├── SAST — Static code analysis (every commit)
├── SCA — Dependency vulnerability scan (every commit)
└── IaCSecret Security Scanscanning — Infrastructure-as-codeDetect reviewleaked credentials (every commit)
Build / CI Phase
├── SAST — Full codebase scan (every PR)
├── SCA — LicenseFull +dependency vulnerability checkaudit (every PR)
├── Container scanning — Image vulnerability analysis (every build)
└── Secret scanning — DetectFull leakedrepo credentialsscan (every commit)PR)
Deployment Phase
├── DAST — DynamicOWASP ZAP scan against staging (every deployment)
└── API securitysecurity: scanOrg-scope —isolation Endpoint fuzzingtests (every deployment)
Operational Phase
├── IAST — Runtime analysis in staging (continuous)
├── Vulnerability assessment — External attack surface (quarterly)
├└── Penetration test — Manual expert testing (annual)annual, └──Phase Red team exercise (annual for critical systems)2+)
3. Testing Types, Tools & Schedule
3.1 SAST — Static Application Security Testing
Purpose: Detect security vulnerabilities in TypeScript/JavaScript source code at development time.code.
| Property | Value |
|---|---|
eslint-plugin-security |
|
| Frequency | Every commit ( |
| Languages |
|
| Integration | |
| Blocking | YES — PR cannot merge if Critical or High findings |
SAST rules in scope:
- OWASP Top 10 (A01-A10)
- SQL
injection,injectioncommand injection, LDAP injectionpatterns - XSS patterns (
reflected,dangerouslySetInnerHTMLstored,withoutDOM-based)sanitization) PathHardcodedtraversalsecrets /localAPIfile inclusionkeys- Insecure
deserializationJWT usage (no algorithm verification, expired tokens not rejected) HardcodedRawcredentialsSQL/insecretsPrisma ($queryRawwith user input)InsecureMissingcryptographyorg-scopeusageWHERE clausesAuthenticationNo-eval,andno-unsafe-regexsession management flawsInsecure direct object referencesrules
Configuration:ESLint configuration:
# .sast-config.yml
severity_threshold: medium # Block on medium and above
exclude_paths:
-{
"**/__tests__/**extends": ["
-plugin:security/recommended"],
"**/node_modules/**"rules": -{
"**/*.test.ts"security/detect-object-injection": custom_rules:"warn",
-"security/detect-non-literal-regexp": {{CUSTOM_RULE_FILE}"error",
"security/detect-possible-timing-attacks": "error",
"no-eval": "error",
"@typescript-eslint/no-explicit-any": "error"
}
}
False positive handling: Developers may suppress findings with an inline comment — requires reviewer approval and explanation. All suppressions are audited quarterly.
3.2 DAST — Dynamic Application Security Testing
Purpose: Detect runtime vulnerabilities by sending attack traffic to a running instance.
| Property | Value |
|---|---|
| Frequency | Every deployment to staging + weekly full scan |
| Target | https://staging.bilko.io — NEVER production without |
| Integration | Post-deployment CI step |
| Blocking | YES — deployment halted if Critical finding |
DAST scan scope:scope for Bilko:
- All API endpoints (authenticated + unauthenticated)
- Authentication
flowsflows:(login,registration,register, passwordreset)reset, 2FA - Invoice CRUD endpoints (create, update, delete)
- Expense endpoints
- VAT report generation
- File upload (receipt attachments)
- SEF/HR-FISK submission endpoints
SearchOrg-scopeand filter parametersError handlingisolation (stackcross-tenanttraceaccessdisclosure)attempts)- Security headers presence and correctness
- SSL/TLS configuration
Critical Bilko-specific DAST authentication setup:tests:
#1. dast-auth.ymlCross-tenant loginUrl:IDOR https: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 /staging.api/v1/invoices (create)
- Expected: 403 Forbidden
- Attempt DELETE /api/v1/invoices/{{DOMAIN}}/auth/loginid}
loginRequestData:- email:Expected: "{{DAST_TEST_USER_EMAIL}}"403 password:Forbidden
"{{DAST_TEST_USER_PASSWORD}}"3. indicateSuccessPattern:SQL "access_token"injection scanAsAuthenticatedUser:via truePrisma:
- 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
Production DAST: Requires written approval from CISO + Engineering Lead. Scoped to non-destructive read-only tests only. Scheduled during off-peak hours.
3.3 SCA — Software Composition Analysis
Purpose: Identify known vulnerabilities (CVEs) in open source dependencies. Verify license compliance.
| Property | Value |
|---|---|
npm + Dependabot |
|
| Frequency | Every commit (Dependabot) + weekly full npm audit |
| Blocking | YES — PR cannot merge with Critical CVE (unresolved) |
| License check | YES — copyleft licenses |
Dependency update policy:
- Security
patches:patches (Critical/High CVE): Merge within 24h / 7 days per remediation SLA(see §4) - Minor updates: Merge within 14 days
- Major updates: Planned migration within 90 days
Approved license list:licenses: MIT, Apache 2.0, BSD (2-clause, 3-clause), ISC,ISC
MPL 2.0
Requires legal review: GPL, LGPL, AGPL, EUPL, CDDLAGPL
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 IAST — Interactive ApplicationVitest Security TestingTests
Purpose: RuntimeBilko-specific automated security analysisregression duringtests integration/acceptancefor testing.accounting Combines benefits of SAST and DAST.isolation.
| Property | Value |
|---|---|
| Blocking |
3.5 Container / Infrastructure Scanning
| YES — | |
ContainerRequired hardeningsecurity requirements:test cases:
// Non-root1. userRBAC intests Dockerfile — Read-onlyall rootrole/action filesystemcombinations
where possible
No secrets in image layers
Base image: distroless or minimaldescribe('RBAC', (Alpine) ) No=> unnecessary{
packages 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.65 Penetration Testing
Purpose: Expert manual security assessment simulating a real attacker.assessment.
| Property | Value |
|---|---|
| Frequency | Annual minimum; after major architectural changes |
| Provider | |
| Methodology | OWASP Testing Guide v4.2 + PTES |
| Blocking | Critical findings: system taken offline until remediated |
Penetration test scope template:for Bilko:
In-scope:
- Production URLs:Production: https://{{DOMAIN}},bilko.io, https://api.{{DOMAIN}}bilko.io
- Authentication flows (login, registration,register, MFA,2FA, SSO)password reset)
- All authenticated API endpoints
- Multi-tenant isolation (IDOR across organizations)
- File upload functionality(receipt attachments)
- AdministrativeSEF functionsand HR-FISK submission endpoints
- CloudFinancial infrastructurecalculation integrity (read-onlyVAT, reconnaissance)double-entry)
Out-of-scope:
- Denial of service attacks
- Railway and Vercel infrastructure (cloud provider responsibility)
- Cloudflare infrastructure
- Social engineering of staff without prior approval
- Physical penetration testing (unless separately scoped)
- Third-party servicesSEF/FINA (AWS infrastructure itself, Stripe, etc.)
- Automated scanning beyond initial reconnaissance (provider uses their own tools)systems
Rules of engagement:
- Testing window: {{START_DATE}}Agreed toin {{END_DATE}}
- Notifywriting before testing: {{CONTACT}} via {{METHOD}}engagement
- Halt on critical finding: Immediately notify {{SECURITY_CONTACT}}[email protected]
- Test accounts provided: Separate test organizations in staging
3.7 Red Team Exercises
Purpose: Adversarial simulation to test detection and response capabilities, not just prevention.
4. Vulnerability Classification & Remediation SLAs
4.1 Severity Classification (CVSS v3.1)
| Severity | CVSS |
Definition | Bilko Example |
|---|---|---|---|
| Critical | 9. |
Remote code execution, full auth bypass, mass data |
|
| High | 7. |
Privilege escalation, cross-tenant IDOR, authenticated auth bypass |
|
| Medium | 4. |
Limited data exposure, CSRF, |
|
| Low | 0. |
Minor |
|
| Informational | N/A | Best practice |
4.2 Remediation SLAs
| Severity | SLA | Action on Breach |
|---|---|---|
| Critical | Immediate containment |
|
| High | 7 calendar days | Escalate to |
| Medium | 30 calendar days | Engineering lead tracks. |
| Low | 90 calendar days | |
| Informational | Next sprint (best effort) |
SLACross-tenant tracking:IDOR (High+) is treated as Critical for Bilko All— findingsfinancial indata {{ISSUE_TRACKER}}exposure withfor labelany organization security-finding+triggers severityimmediate label.
Exceptionincident process: Written request to Security Lead → CISO approval → compensating control required → max extension = 1× original SLA.response.
5. Security Code Review Checklist
Required
Everyfor every PR touching authentication, authorization,RBAC, payment,financial PII,calculations, or cryptography requires a security review:PII:
Authentication & Authorization:
- No hardcoded
credentialssecretsorinsecrets Password hashing uses Argon2id or bcrypt (cost ≥ 12)code- JWT
validation:validated: signature, expiry,issuer,iss,audienceaud -
Authorizationorg-scopedcheckedWHERE clause onEVERYeveryprotectedPrismaendpointquery(nottouchingjustmulti-tenantat route level)data -
NoRBACisAdminrequireRole()flagmiddlewarefromapplieduser-suppliedoninputevery protected endpoint -
Session2FAtokenscodeinvalidatedvalidationonuseslogoutconstant-time comparison
Input Validation:
-
AllZoduserschemainputsforvalidatedall(type,requestlength, format, allowed values)bodies -
ParameterizedPrisma queriesforonlyall—database operations (nostring$queryRawconcatenation)with user input - File uploads: type
validated by magic bytes, filename sanitized,validated, storedoutsidein R2 not webroot - No
eval(),stringexec(),interpolation in SQL orshellsystemcommandcommands
Financial userIntegrity:
- All monetary values use
Decimal/NUMERIC— nonumberorfloat -
HTMLVAToutputratesproperlyvalidatedescapedagainst country code (nonotinnerHTMLuser-supplied) -
userDouble-entry:data)debit = credit enforced - Exchange rates locked at transaction date
Cryptography:
- No MD5, SHA-1, DES, RC4 anywhere
- Random values
generatedfromwithcrypto.randomBytes()CSPRNG— notMath.random() -
IVs/noncesbcryptarecostrandomfactorand=never12reusedfor any new password hashing - Private keys / secrets not in source code or logs
Error Handling:
No stack traces or internal details in API error responsesErrors logged internally but not exposed externallyNo user enumeration (same response for "user not found" and "wrong password")
Data Handling:
-
PIITaxnotIDslogged(PIB/JMBG/OIB/JIB)inandplaintextIBAN encrypted before storage - Sensitive data not in query parameters
(useorPOST body)URLs -
NoPIIunnecessarynotdatainretentionSentry error reports (datauseminimization).addEventProcessor()to scrub) -
ProperLoggedActiondata deletion/anonymization on account close
Dependencies:
New dependency reviewedentries forknownallCVEsfinancialbefore addingNo end-of-life dependencies introducedLicense compatible with project licensemutations
6. Bug Bounty Program
Status: {{ACTIVE / PLANNED / NOT_APPLICABLE}}
Platform: {{HACKERONE / BUGCROWD / INTIGRITI}}
Scope: {{IN_SCOPE_URLS}}
Out of scope: {{OUT_OF_SCOPE}}
Reward structure:
Response SLAs (to researcher):
Acknowledgment: 1 business dayTriage/validation: 5 business daysRemediation update: Per §4.2 SLAsReward payment: Within 14 days of fix deployment
Safe harbor: Researchers acting in good faith following disclosure policy are protected from legal action.
7. Security Testing in CI/CD Pipeline
flowchart LR
COMMIT[Developer\nCommit] -->|Pre-commit hook|commit| SAST_INC[SAST\nIncremental]ESLint\nSAST incremental]
SAST_INC -->|Pass| PUSH[Git Push]
PUSH --> PR[Pull Request]
PR --> SAST_FULL[SAST\nFullESLint Scan]Full\n+ TypeScript strict]
PR --> SCA[SCA\nDependency]npm audit\nDependabot]
PR --> SECRET[Secret\nScanning]
PR --> IAC[IaC\VITEST[Vitest\nSecurity Scan]tests]
SAST_FULL & SCA & SECRET & IACVITEST -->|All pass| BUILD[Build\nContainer]Build]
BUILD --> CONTAINER_SCAN[Container\nScan]
CONTAINER_SCAN -->|Pass| STAGING[Deploy to\nStaging]Staging]
STAGING --> DAST[DAST\nScan]OWASP ZAP\nDAST scan]
STAGING --> IAST[IAST\nRuntime]E2E[Playwright\nIsolation tests]
DAST & IASTE2E -->|Pass| GATE{Security\nGate}Security Gate}
GATE -->|Pass| PROD[Deploy to\nProduction]Production]
GATE -->|Fail| BLOCK[Block Deploy\nAlert Security][email protected]]
Pipeline security gate criteria:
| Gate | Tool | Blocking Criteria |
|---|---|---|
| Pre-commit | Critical findings in changed files | |
| PR gate 1 | Any Critical or High |
|
| PR gate 2 | Critical CVE |
|
| PR gate 3 | Secret |
Any detected secret |
| PR gate 4 | ||
| Post-staging | Critical or High dynamic finding | |
| Post-staging | Playwright E2E isolation | Cross-tenant access succeeds (should fail) |
8. Reporting Format
8.1 Individual Finding Report
Finding ID: VULN-{{YEAR}}-{{SEQUENCE}}
Title: {{SHORT_DESCRIPTION}}
Severity: Critical / High / Medium / Low
CVSS Score: {{SCORE}} (v3.1)
CVSS Vector: {{VECTOR_STRING}}
Description:
{{DETAILED_DESCRIPTION_OF_VULNERABILITY}}
Affected Systems:
- {{SYSTEM_1}}: {{URL_OR_CODE_LOCATION}}
- {{SYSTEM_2}}: {{URL_OR_CODE_LOCATION}}
Proof of Concept:
{{STEPS_TO_REPRODUCE}}
{{SCREENSHOTS_OR_CURL_COMMANDS}}
Impact:
{{WHAT_AN_ATTACKER_CAN_DO_IF_EXPLOITED}}
Remediation:
{{SPECIFIC_REMEDIATION_STEPS}}
References:
- CWE-{{N}}: {{CWE_DESCRIPTION}}
- OWASP: {{OWASP_CATEGORY}}
- CVE-{{N}} (if applicable)
Owner: {{ASSIGNED_ENGINEER}}
SLA: {{DUE_DATE}}
Status: Open / In Progress / Resolved / Accepted Risk
8.2 Security Testing Summary Report
Produced after each: Penetration test, quarterly DAST, quarterly vulnerability assessment.
Report period: {{START_DATE}} to {{END_DATE}}
Report type: Penetration Test / DAST / Vulnerability Assessment
Conducted by: {{INTERNAL / EXTERNAL_FIRM}}
EXECUTIVE SUMMARY
Total findings: Critical: {{N}} | High: {{N}} | Medium: {{N}} | Low: {{N}}
Findings resolved within SLA: {{N}}/{{TOTAL}} ({{PERCENT}}%)
Risk trend vs previous period: Improving / Stable / Worsening
TOP FINDINGS BY SEVERITY
[Table of Critical and High findings]
REMEDIATION STATUS
[Table of all findings with status and owner]
RECOMMENDATIONS
1. {{STRATEGIC_RECOMMENDATION_1}}
2. {{STRATEGIC_RECOMMENDATION_2}}
9.7. Metrics & KPIs
| Metric | Target | |
|---|---|---|
| Critical findings resolved within SLA | 100% | Monthly |
| High findings resolved within SLA | Monthly | |
| < 24 hours | ||
| MTTR — High | < 7 days | Monthly |
| 100% | Monthly | |
| Vitest security test pass rate | 100% | Every deployment |
| Open High+ findings (backlog) | Monthly | |
| npm audit — Critical CVEs | 0 | Weekly |
| Penetration test — |
100% within 30 days | Per test |
Security metrics dashboard:tracking: {{DASHBOARD_URL}}GitHub Issues with security label
Quarterly security report: Published to {{RECIPIENTS}}CEO onand {{SCHEDULE}}engineering team
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | Compliance Architect | 2026-02-23 | |
| Engineering Lead | |||