# BOC Security Readiness Report > **Datum:** 2026-08-10 > **Auditor:** Bernt (AI Security Audit) > **System:** BOC (Business Operations Center) > **Scope:** Full stack — backend (Go), frontend (React/Vite), infrastructure, deployment --- ## EXECUTIVE SUMMARY **SLUTSTATUS: SECURITY NOT READY** Systemet har **flera kritiska säkerhetsbrister** som blockerar produktionsdeplojering. De mest allvarliga är: 1. **Secrets i Git-historik** — JWT_SECRET och DB_PASSWORD committade 2. **Autentisering är bruten** — debug-endpoint genererar admin-tokens, fallback till HS256 med hårdkodad secret 3. **Ingen auktorisation** — alla autentiserade användare har tillgång till all data (IDOR) 4. **SQL injection** — strängkonkatenering i journal.go countQuery 5. **XSS** — dangerouslySetInnerHTML med osaniterat innehåll 6. **Ingen tenant isolation** — multi-tenancy är ej implementerat trots påstödd support 7. **CORS tillåter wildcard** — med credentials=true --- ## 1. SECRETS & CREDENTIALS ### 🔴 CRITICAL — .env-fil med secrets i Git **Problem:** `.env` innehåller: ``` JWT_SECRET=aamos-…tion DB_PASSWORD=boc_secret_2026 ``` **Fil:** `boc/.env` (committad i Git, commit af874040c) **Severity:** CRITICAL **Attackvektor:** Alla med läsåtkomst till repot har full åtkomst till JWT-signering och databas. **Åtgärd:** 1. Radera `.env` från Git-historiken (git filter-repo eller BFG Repo-Cleaner) 2. Rotera JWT_SECRET och DB_PASSWORD omedelbart 3. Lägg till `.env` i `.gitignore` 4. Använd miljövariabler injicerade av deployment-plattform **Regression test:** ```bash git log --all --full-history -- .env # ska returnera inget ``` ### 🔴 CRITICAL — Hårdkodade credentials i källkod **Problem:** `config/config.go` har hårdkodade fallback-värden: ```go LedgerDBURL: "postgres://wavult_admin:efG15aKjqgu7uotZoAiLTRBtBDMoXITxIe9Hi6EB@platform-identity-core.cvi0qcksmsfj.eu-north-1.rds.amazonaws.com:5432/amos?sslmode=disable" JWTSecret: "w+Qkf/CoDda3Ba7vZLKokrGHiwUV5Ak/3tiBmFAvRC8=" ``` **Severity:** CRITICAL **Attackvektor:** Källkoden är publik (eller kan läcka). Credentials finns i binären. **Åtgärd:** 1. Ta bort ALLA fallback-värden för secrets 2. Använd `requireEnv()` för alla secrets 3. Panic om secret saknas — tvinga explicit konfiguration ### 🟡 MEDIUM — Docker Compose exponerar secrets **Problem:** `docker-compose.yml` har: ```yaml JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set} ``` Men miljövariabler syns i `docker inspect` och process-listor. **Åtgärd:** Använd Docker secrets eller extern secret manager (AWS Secrets Manager, HashiCorp Vault). --- ## 2. AUTHENTICATION ### 🔴 CRITICAL — Debug-endpoint genererar admin-tokens **Problem:** `/debug/token` finns aktivt: ```go if cfg.Port == "9092" || cfg.Port == "9096" { r.Get("/debug/token", handlers.DebugTokenHandler(cfg.JWTSecret)) } ``` **Fil:** `backend/main.go:145-147` **Severity:** CRITICAL **Attackvektor:** Vem som helst kan generera en giltig admin-token genom att anropa `/debug/token`. **Åtgärd:** 1. Ta bort debug-endpoint helt 2. Om nödvändigt för utveckling — kräv env-var `ENABLE_DEBUG=true` och logga varning ### 🔴 CRITICAL — Login-endpoint genererar token utan lösenordskontroll **Problem:** `/api/v1/auth/login`: ```go r.Post("/api/v1/auth/login", func(w http.ResponseWriter, r *http.Request) { // ...decode email... token, err := jwtService.GenerateToken("3847477b-3d56-4975-9157-ae8f9ce52aa7", req.Email, "admin") // Returnerar admin-token för VILKEN EMAIL SOM HELST }) ``` **Fil:** `backend/main.go:149-175` **Severity:** CRITICAL **Attackvektor:** Vem som helst kan logga in som admin med valfri email. **Åtgärd:** 1. Implementera riktig lösenordsverifiering mot databas 2. Använd bcrypt.CompareHashAndPassword 3. Returnera generiskt felmeddelande oavsett om email eller lösenord är fel ### 🔴 CRITICAL — JWT fallback till HS256 med hårdkodad secret **Problem:** `middleware/jwt.go`: ```go // Fallback: Tillåt HS256 tokens för utveckling jwtSecret := os.Getenv("JWT_SECRET") if jwtSecret == "" { jwtSecret = "w+Qkf/CoDda3Ba7vZLKokrGHiwUV5Ak/3tiBmFAvRC8=" } ``` **Severity:** CRITICAL **Attackvektor:** Angripare kan signera egna HS256-tokens med den hårdkodade secret. **Åtgärd:** 1. Ta bort HS256-fallback helt 2. Kräv RS256 med JWKS från ouroboros-identity 3. Panic om JWKS_URL saknas i produktion ### 🟡 MEDIUM — Token lagras i localStorage **Problem:** Frontend lagrar JWT i localStorage: ```typescript localStorage.setItem('amos_token', token) ``` **Risk:** XSS kan stjäla token. Men eftersom systemet redan har XSS-brister (se §6) är detta förstärkande. **Åtgärd:** 1. Använd httpOnly cookies för tokens 2. Implementera CSRF-skydd om cookies används 3. Eller: Använd token rotation med refresh tokens ### 🟡 MEDIUM — Token har för lång livstid **Problem:** Token är giltig i 30 dagar: ```go "exp": now.Add(30 * 24 * time.Hour).Unix() ``` **Åtgärd:** Minska till 15-60 minuter. Implementera refresh tokens. --- ## 3. AUTHORIZATION / ACCESS CONTROL ### 🔴 CRITICAL — Ingen resource-level authorization (IDOR) **Problem:** INGEN handler verifierar att användaren äger resursen. Exempel: **crm.go GetCustomer:** ```go func (h *CRMHandler) GetCustomer(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") // Ingen kontroll av vem som frågar! err := h.DB.QueryRow(`SELECT ... FROM boc_customers WHERE id = $1`, id) } ``` **hr.go GetEmployee:** ```go func (h *HRHandler) GetEmployee(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") // Ingen kontroll! err := h.DB.QueryRow(`SELECT ... FROM boc_employees WHERE id = $1`, id) } ``` **Severity:** CRITICAL **Attackvektor:** Användare A kan läsa/använda User B:s customers, employees, deals, invoices, etc. genom att bara byta ID. **Åtgärd:** 1. Lägg till `tenant_id` eller `org_id` på ALLA queries 2. Verifiera att claims.OrgID matchar resursens tenant 3. Exempel: ```go claims, _ := middleware.FromContext(r.Context()) err := h.DB.QueryRow(`SELECT ... FROM boc_customers WHERE id = $1 AND tenant_id = $2`, id, claims.OrgID) ``` ### 🔴 CRITICAL — RBAC middleware används inte **Problem:** `middleware/security.go` definierar `RBACMiddleware` men den används INGENSTANS i `main.go`. Alla routes under `r.Use(authMiddleware)` har samma åtkomst för alla autentiserade användare. **Åtgärd:** 1. Applicera RBAC på alla routes: ```go r.With(middleware.RBACMiddleware(middleware.ResCustomers, middleware.PermRead)) .Get("/api/v1/crm/customers", crmH.ListCustomers) ``` ### 🔴 CRITICAL — Admin-panel saknar admin-verifiering **Problem:** `/api/v1/amos/engines/{id}/restart` och liknande admin-endpoints har ingen roll-kontroll. **Åtgärd:** Lägg till `authService.RequireRole("admin")` på alla admin-endpoints. --- ## 4. DATABASE SECURITY ### 🔴 CRITICAL — SQL injection i journal.go **Problem:** `backend/handlers/journal.go` — countQuery använder strängkonkatenering: ```go countQuery := `SELECT COUNT(*) FROM journal_entries WHERE 1=1` if accountFilter != "" { countQuery += ` AND EXISTS ( SELECT 1 FROM journal_lines jl JOIN accounts a ON jl.account_id = a.id WHERE jl.journal_entry_id = journal_entries.id AND a.code = '` + accountFilter + `' )` } ``` **Severity:** CRITICAL **Attackvektor:** `accountFilter` kan innehålla SQL injection. **Åtgärd:** Använd parameterized queries: ```go countQuery := `SELECT COUNT(*) FROM journal_entries WHERE 1=1` if accountFilter != "" { countQuery += ` AND EXISTS ( SELECT 1 FROM journal_lines jl JOIN accounts a ON jl.account_id = a.id WHERE jl.journal_entry_id = journal_entries.id AND a.code = $1 )` args = append(args, accountFilter) } ``` ### 🟡 MEDIUM — Ingen RLS (Row Level Security) **Problem:** PostgreSQL RLS är inte aktiverat. Alla queries körs med samma databasanvändare. **Åtgärd:** 1. Aktivera RLS på alla tabeller: ```sql ALTER TABLE boc_customers ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON boc_customers USING (tenant_id = current_setting('app.current_tenant')::UUID); ``` ### 🟢 PASS — Parameterized queries används i de flesta fall De flesta handlers använder `$1, $2` etc. korrekt. --- ## 5. INPUT VALIDATION ### 🟡 MEDIUM — Bristfällig input-validering **Problem:** Många handlers validerar bara grundläggande format (email regex) men inte: - Maxlängd på strängar - Tillåtna värden för enums (status, stage) - Numeriska range - SQL wildcards i sökparametrar **Exempel:** `crm.go CreateCustomer` validerar inte längd på name, company, etc. **Åtgärd:** Implementera schema-baserad validering med ett bibliotek som `go-playground/validator`. --- ## 6. XSS / HTML / CONTENT SECURITY ### 🔴 CRITICAL — dangerouslySetInnerHTML med osaniterat innehåll **Problem:** `web-v2/src/pages/LegalPage.tsx`: ```tsx
``` `renderPlaceholders` ersätter `{{variable}}` med `value` men saniterar INTE `value`. **Severity:** CRITICAL **Attackvektor:** Om en contract variable innehåller `` körs det. **Åtgärd:** 1. Använd DOMPurify innan dangerouslySetInnerHTML 2. Eller bättre: rendera utan HTML, använd React-komponenter ### 🟡 MEDIUM — CSP är för restriktiv men saknar viktiga direktiv **Problem:** `middleware/security.go`: ```go w.Header().Set("Content-Security-Policy", "default-src 'self'") ``` Detta blockerar inline-scripts men frontend använder möjligen inline (vite byggda bundles). **Åtgärd:** 1. Generera nonce-baserad CSP 2. Eller använd hash-baserad CSP för kända scripts --- ## 7. CORS ### 🔴 CRITICAL — CORS tillåter wildcard med credentials **Problem:** `middleware/cors.go`: ```go for _, o := range origins { if o == "*" || o == origin { allowed = true break } } if allowed { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Credentials", "true") } ``` Om `CORSOrigins` innehåller `"*"` (vilket är default i config.go: `CORSOrigins: []string{"http://localhost:3000"}` men kan ändras), tillåts wildcard med credentials. **Severity:** CRITICAL **Attackvektor:** CSRF-liknande attacker från vilken domän som helst. **Åtgärd:** 1. Förbjud `"*"` i CORS-konfiguration 2. Kräv explicita origins 3. Validera origin strikt --- ## 8. RATE LIMITING / ABUSE PROTECTION ### 🟡 MEDIUM — Rate limiting är för generöst **Problem:** `middleware/security.go`: ```go limiter = rate.NewLimiter(rate.Every(time.Second), 10) // 10 req/s ``` Detta är per IP och gäller alla endpoints. Login har inget separat rate limit. **Åtgärd:** 1. Separata limits för olika endpoints: - Login: 5 försök / 15 minuter - API: 100 req / minut - Expensive operations: 10 req / minut 2. Använd Redis-baserad rate limiting för distribuerade deployment --- ## 9. FILE UPLOAD SECURITY ### 🟢 PASS — Ingen filuppladdning hittad Systemet verkar inte ha filuppladdningsfunktionalitet i nuläget. --- ## 10. IDs & RESOURCE ACCESS ### 🔴 CRITICAL — Predictable integer IDs används inte, men UUID skyddar inte **Problem:** Systemet använder UUID (bra) men verifierar INTE ownership (kritiskt). **Åtgärd:** Se §3 — lägg till tenant_id/org_id på alla queries. --- ## 11. WEBHOOK SECURITY ### 🟢 PASS — Inga webhooks hittade Systemet verkar inte ha webhook-funktionalitet i nuläget. --- ## 12. LOGGING & ERROR HANDLING ### 🟡 MEDIUM — Error messages exponerar intern information **Problem:** Vissa handlers returnerar databasfel direkt: ```go writeError(w, http.StatusInternalServerError, "failed to create deal: "+err.Error()) ``` **Åtgärd:** Returnera generiska felmeddelanden till klienten, logga detaljer server-side. ### 🟡 MEDIUM — Audit log saknar user_id korrekt **Problem:** `middleware/security.go`: ```go if userID, ok := r.Context().Value("user_id").(string); ok { event.UserID = userID } ``` Men context-nyckeln är `"user"` i auth middleware, inte `"user_id"`. **Åtgärd:** Använd konsekventa context-nycklar. --- ## 13. PASSWORD SECURITY ### 🟢 PASS — bcrypt används `auth.go` använder `bcrypt.CompareHashAndPassword` korrekt. ### 🟡 MEDIUM — Ingen password strength policy **Åtgärd:** Implementera minst 8 tecken, blandade case, siffror, specialtecken. --- ## 14. DEPENDENCIES ### 🟡 MEDIUM — Dependencies behöver audit **Kända paket:** - `github.com/golang-jwt/jwt/v5` — OK, senaste - `golang.org/x/crypto` — OK, senaste - `github.com/go-chi/chi/v5` — OK - `github.com/prometheus/client_golang` — OK **Åtgärd:** Kör `govulncheck` regelbundet i CI/CD. --- ## 15. CSRF / SESSION SECURITY ### 🔴 CRITICAL — Ingen CSRF-skydd **Problem:** Systemet använder JWT i header (bra för CSRF-resistens) MEN frontend lagrar i localStorage och skickar via fetch. Om systemet byter till cookies (rekommenderat) behövs CSRF-skydd. **Åtgärd:** 1. Om cookies: implementera Double Submit Cookie eller Synchronizer Token 2. Om JWT i header: säkerställ att header alltid skickas --- ## 16. SSRF / SERVER-SIDE REQUESTS ### 🟡 MEDIUM — LandvexRealHandler kan vara sårbar för SSRF **Problem:** `backend/handlers/landvex_real.go`: ```go func (h *LandvexRealHandler) fetchFromLandvex(endpoint string) (map[string]interface{}, error) { resp, err := h.client.Get(h.baseURL + endpoint) ``` `endpoint` kontrolleras inte. Om detta anropas med användar-kontrollerad input kan det leda till SSRF. **Åtgärd:** Validera endpoint mot allowlist. --- ## 17. COMMAND / CODE / TEMPLATE INJECTION ### 🟢 PASS — Ingen dynamisk kodexekvering hittad --- ## 18. PATH TRAVERSAL ### 🟢 PASS — Ingen filsystemåtkomst med användarkontrollerade paths hittad --- ## 19. SECURITY HEADERS ### 🟡 MEDIUM — Security headers är delvis implementerade **Finns:** - X-Content-Type-Options: nosniff - X-Frame-Options: DENY - X-XSS-Protection: 1; mode=block - Referrer-Policy: strict-origin-when-cross-origin - CSP: default-src 'self' **Saknas:** - Strict-Transport-Security (HSTS) - Permissions-Policy **Åtgärd:** Lägg till: ```go w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") ``` --- ## 20. TRANSPORT SECURITY ### 🟡 MEDIUM — Server kör HTTP (inte HTTPS) **Problem:** `main.go`: ```go srv := &http.Server{Addr: ":" + cfg.Port, Handler: r} ``` Ingen TLS-konfiguration. **Åtgärd:** 1. Terminera TLS vid load balancer (nginx/traefik) ELLER 2. Konfigurera TLS direkt i Go-servern --- ## 21. ADMIN SECURITY ### 🔴 CRITICAL — Admin-endpoints saknar admin-verifiering **Problem:** `/api/v1/amos/engines/{id}/restart` har ingen roll-kontroll. **Åtgärd:** Lägg till `RequireRole("admin")` middleware. --- ## 22. MULTI-TENANCY ### 🔴 CRITICAL — Tenant isolation är ej implementerat **Problem:** 1. `tenant_id` finns i modeller men används inte i queries 2. `crm.go`, `hr.go`, `sales.go` etc. filtrerar inte på tenant 3. `SwitchTenant` uppdaterar ingen session **Åtgärd:** 1. Lägg till tenant_id-filter på ALLA databasqueries 2. Implementera tenant-kontext i middleware 3. Verifiera att användaren har tillgång till tenant --- ## 23. API SECURITY ### 🔴 CRITICAL — Flera endpoints saknar authentication **Problem:** Alla routes under `r.Group(func(r chi.Router) { r.Use(authMiddleware) ... })` är skyddade, MEN: - `/health` och `/api/v1/health` är publika (OK) - `/metrics` är publik — exponerar intern data - `/debug/token` är publik (om port matchar) **Åtgärd:** 1. Skydda `/metrics` med API-nyckel eller IP-restriction 2. Ta bort `/debug/token` --- ## 24. SECURITY TESTING ### 🔴 CRITICAL — Inga automatiserade security tests **Problem:** Inga tester för: - IDOR - SQL injection - XSS - Authentication bypass - Rate limiting **Åtgärd:** Skapa security test suite. --- ## 25. CI/CD SECURITY GATE ### 🔴 CRITICAL — Ingen CI/CD security gate **Problem:** Inga automatiska säkerhetskontroller i byggprocessen. **Åtgärd:** 1. Lägg till secret scanning (gitleaks, truffleHog) 2. Lägg till dependency scanning (govulncheck, Snyk) 3. Lägg till static analysis (gosec, semgrep) --- ## 26. SECURITY AUDIT — SAMMANSTÄLLNING | # | Problem | Severity | Status | |---|---------|----------|--------| | 1 | Secrets i Git (.env) | CRITICAL | 🔴 OÅTGÄRDAT | | 2 | Hårdkodade credentials i källkod | CRITICAL | 🔴 OÅTGÄRDAT | | 3 | Debug-endpoint genererar admin-tokens | CRITICAL | 🔴 OÅTGÄRDAT | | 4 | Login utan lösenordskontroll | CRITICAL | 🔴 OÅTGÄRDAT | | 5 | JWT HS256 fallback med hårdkodad secret | CRITICAL | 🔴 OÅTGÄRDAT | | 6 | Ingen resource-level authorization (IDOR) | CRITICAL | 🔴 OÅTGÄRDAT | | 7 | RBAC middleware används inte | CRITICAL | 🔴 OÅTGÄRDAT | | 8 | SQL injection i journal.go | CRITICAL | 🔴 OÅTGÄRDAT | | 9 | XSS via dangerouslySetInnerHTML | CRITICAL | 🔴 OÅTGÄRDAT | | 10 | CORS tillåter wildcard med credentials | CRITICAL | 🔴 OÅTGÄRDAT | | 11 | Ingen tenant isolation | CRITICAL | 🔴 OÅTGÄRDAT | | 12 | Admin-endpoints saknar admin-verifiering | CRITICAL | 🔴 OÅTGÄRDAT | | 13 | Token i localStorage | MEDIUM | 🟡 OÅTGÄRDAT | | 14 | Token för lång livstid (30 dagar) | MEDIUM | 🟡 OÅTGÄRDAT | | 15 | Rate limiting för generöst | MEDIUM | 🟡 OÅTGÄRDAT | | 16 | Error messages exponerar intern info | MEDIUM | 🟡 OÅTGÄRDAT | | 17 | Audit log saknar user_id | MEDIUM | 🟡 OÅTGÄRDAT | | 18 | SSRF-risk i LandvexRealHandler | MEDIUM | 🟡 OÅTGÄRDAT | | 19 | Saknar HSTS header | MEDIUM | 🟡 OÅTGÄRDAT | | 20 | Server kör HTTP | MEDIUM | 🟡 OÅTGÄRDAT | | 21 | /metrics är publik | MEDIUM | 🟡 OÅTGÄRDAT | | 22 | Ingen password strength policy | MEDIUM | 🟡 OÅTGÄRDAT | | 23 | Ingen RLS i PostgreSQL | MEDIUM | 🟡 OÅTGÄRDAT | | 24 | Bristfällig input-validering | MEDIUM | 🟡 OÅTGÄRDAT | | 25 | Ingen CSRF-skydd | MEDIUM | 🟡 OÅTGÄRDAT | | 26 | Ingen CI/CD security gate | CRITICAL | 🔴 OÅTGÄRDAT | | 27 | Inga automatiserade security tests | CRITICAL | 🔴 OÅTGÄRDAT | --- ## 27. FINAL SECURITY GATE ### Adversarial Review **Fråga:** "Om en angripare har internetåtkomst, ett vanligt användarkonto och känner till hela frontendens implementation — hur kan denne få åtkomst till något användaren inte borde kunna komma åt?" **Svar:** Flera vägar: 1. **Anropa `/debug/token`** → få admin-token → full åtkomst till allt 2. **Anropa `/api/v1/auth/login`** med valfri email → få admin-token 3. **Byta ID i URL** → läsa andra företags kunder, anställda, avtal 4. **SQL injection via account-filter** → läsa hela databasen 5. **XSS via contract variables** → stjäla andra användares tokens 6. **CORS wildcard** → CSRF-attacker från vilken sida som helst --- ## REKOMMENDERADE ÅTGÄRDER (Prioriterade) ### Omedelbart (Blockerar produktion): 1. ✅ Ta bort `/debug/token` 2. ✅ Fixa `/api/v1/auth/login` — kräv lösenordsverifiering 3. ✅ Ta bort HS256-fallback, kräv RS256 4. ✅ Ta bort secrets från Git, rotera alla secrets 5. ✅ Fixa SQL injection i journal.go 6. ✅ Ta bort dangerouslySetInnerHTML eller använd DOMPurify 7. ✅ Fixa CORS — förbjud wildcard ### Inom 1 vecka: 8. ✅ Implementera tenant isolation på ALLA queries 9. ✅ Implementera resource-level authorization (IDOR-skydd) 10. ✅ Applicera RBAC på alla routes 11. ✅ Skydda admin-endpoints 12. ✅ Fixa audit log user_id ### Inom 1 månad: 13. ✅ Implementera proper session-hantering (httpOnly cookies) 14. ✅ Minska token-livstid 15. ✅ Förbättra rate limiting 16. ✅ Lägg till input-validering 17. ✅ Aktivera RLS i PostgreSQL 18. ✅ Implementera CI/CD security gates 19. ✅ Skriv security tests --- ## SLUTSTATUS **SECURITY NOT READY** Systemet får INTE deployas till produktion i nuvarande skick. Flera kritiska säkerhetsbrister möjliggör fullständig kompromettering av systemet. **Blockerande issues:** 12 CRITICAL **Medium issues:** 15 **Totalt:** 27 säkerhetsbrister --- *Rapport genererad av Bernt (AI Security Audit)* *Datum: 2026-08-10*