- Add password hashing with bcrypt - Add AuthService with proper login - Add password strength validation - Add RBAC middleware (AdminOnly, ManagerOrAdmin) - Add tenant isolation middleware - Update CRM handler with tenant filtering - Add JWT fallback for development mode - Add user context helpers - Build successful
20 KiB
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:
- Secrets i Git-historik — JWT_SECRET och DB_PASSWORD committade
- Autentisering är bruten — debug-endpoint genererar admin-tokens, fallback till HS256 med hårdkodad secret
- Ingen auktorisation — alla autentiserade användare har tillgång till all data (IDOR)
- SQL injection — strängkonkatenering i journal.go countQuery
- XSS — dangerouslySetInnerHTML med osaniterat innehåll
- Ingen tenant isolation — multi-tenancy är ej implementerat trots påstödd support
- 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:
- Radera
.envfrån Git-historiken (git filter-repo eller BFG Repo-Cleaner) - Rotera JWT_SECRET och DB_PASSWORD omedelbart
- Lägg till
.envi.gitignore - Använd miljövariabler injicerade av deployment-plattform
Regression test:
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:
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:
- Ta bort ALLA fallback-värden för secrets
- Använd
requireEnv()för alla secrets - Panic om secret saknas — tvinga explicit konfiguration
🟡 MEDIUM — Docker Compose exponerar secrets
Problem: docker-compose.yml har:
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:
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:
- Ta bort debug-endpoint helt
- Om nödvändigt för utveckling — kräv env-var
ENABLE_DEBUG=trueoch logga varning
🔴 CRITICAL — Login-endpoint genererar token utan lösenordskontroll
Problem: /api/v1/auth/login:
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:
- Implementera riktig lösenordsverifiering mot databas
- Använd bcrypt.CompareHashAndPassword
- Returnera generiskt felmeddelande oavsett om email eller lösenord är fel
🔴 CRITICAL — JWT fallback till HS256 med hårdkodad secret
Problem: middleware/jwt.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:
- Ta bort HS256-fallback helt
- Kräv RS256 med JWKS från ouroboros-identity
- Panic om JWKS_URL saknas i produktion
🟡 MEDIUM — Token lagras i localStorage
Problem: Frontend lagrar JWT i localStorage:
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:
- Använd httpOnly cookies för tokens
- Implementera CSRF-skydd om cookies används
- Eller: Använd token rotation med refresh tokens
🟡 MEDIUM — Token har för lång livstid
Problem: Token är giltig i 30 dagar:
"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:
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:
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:
- Lägg till
tenant_idellerorg_idpå ALLA queries - Verifiera att claims.OrgID matchar resursens tenant
- Exempel:
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:
- Applicera RBAC på alla routes:
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:
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:
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:
- Aktivera RLS på alla tabeller:
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:
<div
className="text-sm text-text-secondary"
dangerouslySetInnerHTML={{
__html: renderPlaceholders(section.content, selectedContract.variables || {})
}}
/>
renderPlaceholders ersätter {{variable}} med <strong>value</strong> men saniterar INTE value.
Severity: CRITICAL
Attackvektor: Om en contract variable innehåller <script>alert('xss')</script> körs det.
Åtgärd:
- Använd DOMPurify innan dangerouslySetInnerHTML
- Eller bättre: rendera utan HTML, använd React-komponenter
🟡 MEDIUM — CSP är för restriktiv men saknar viktiga direktiv
Problem: middleware/security.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:
- Generera nonce-baserad CSP
- Eller använd hash-baserad CSP för kända scripts
7. CORS
🔴 CRITICAL — CORS tillåter wildcard med credentials
Problem: middleware/cors.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:
- Förbjud
"*"i CORS-konfiguration - Kräv explicita origins
- Validera origin strikt
8. RATE LIMITING / ABUSE PROTECTION
🟡 MEDIUM — Rate limiting är för generöst
Problem: middleware/security.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:
- Separata limits för olika endpoints:
- Login: 5 försök / 15 minuter
- API: 100 req / minut
- Expensive operations: 10 req / minut
- 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:
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:
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, senastegolang.org/x/crypto— OK, senastegithub.com/go-chi/chi/v5— OKgithub.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:
- Om cookies: implementera Double Submit Cookie eller Synchronizer Token
- 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:
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:
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:
srv := &http.Server{Addr: ":" + cfg.Port, Handler: r}
Ingen TLS-konfiguration.
Åtgärd:
- Terminera TLS vid load balancer (nginx/traefik) ELLER
- 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:
tenant_idfinns i modeller men används inte i queriescrm.go,hr.go,sales.goetc. filtrerar inte på tenantSwitchTenantuppdaterar ingen session
Åtgärd:
- Lägg till tenant_id-filter på ALLA databasqueries
- Implementera tenant-kontext i middleware
- 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:
/healthoch/api/v1/healthär publika (OK)/metricsär publik — exponerar intern data/debug/tokenär publik (om port matchar)
Åtgärd:
- Skydda
/metricsmed API-nyckel eller IP-restriction - 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:
- Lägg till secret scanning (gitleaks, truffleHog)
- Lägg till dependency scanning (govulncheck, Snyk)
- 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:
- Anropa
/debug/token→ få admin-token → full åtkomst till allt - Anropa
/api/v1/auth/loginmed valfri email → få admin-token - Byta ID i URL → läsa andra företags kunder, anställda, avtal
- SQL injection via account-filter → läsa hela databasen
- XSS via contract variables → stjäla andra användares tokens
- CORS wildcard → CSRF-attacker från vilken sida som helst
REKOMMENDERADE ÅTGÄRDER (Prioriterade)
Omedelbart (Blockerar produktion):
- ✅ Ta bort
/debug/token - ✅ Fixa
/api/v1/auth/login— kräv lösenordsverifiering - ✅ Ta bort HS256-fallback, kräv RS256
- ✅ Ta bort secrets från Git, rotera alla secrets
- ✅ Fixa SQL injection i journal.go
- ✅ Ta bort dangerouslySetInnerHTML eller använd DOMPurify
- ✅ Fixa CORS — förbjud wildcard
Inom 1 vecka:
- ✅ Implementera tenant isolation på ALLA queries
- ✅ Implementera resource-level authorization (IDOR-skydd)
- ✅ Applicera RBAC på alla routes
- ✅ Skydda admin-endpoints
- ✅ Fixa audit log user_id
Inom 1 månad:
- ✅ Implementera proper session-hantering (httpOnly cookies)
- ✅ Minska token-livstid
- ✅ Förbättra rate limiting
- ✅ Lägg till input-validering
- ✅ Aktivera RLS i PostgreSQL
- ✅ Implementera CI/CD security gates
- ✅ 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