Files
boc/SECURITY_READINESS_REPORT_2026-08-10.md
T
Bernt 78b57273e2 security: Add proper authentication, RBAC, and tenant isolation
- 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
2026-08-10 12:52:48 +00:00

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:

  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:

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:

  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:

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:

  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:

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:

// 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:

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:

"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:

  1. Lägg till tenant_id eller org_id på ALLA queries
  2. Verifiera att claims.OrgID matchar resursens tenant
  3. 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:

  1. 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:

  1. 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:

  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:

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:

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:

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:

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, 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:

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:

  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:

  1. Implementera tenant isolation på ALLA queries
  2. Implementera resource-level authorization (IDOR-skydd)
  3. Applicera RBAC på alla routes
  4. Skydda admin-endpoints
  5. Fixa audit log user_id

Inom 1 månad:

  1. Implementera proper session-hantering (httpOnly cookies)
  2. Minska token-livstid
  3. Förbättra rate limiting
  4. Lägg till input-validering
  5. Aktivera RLS i PostgreSQL
  6. Implementera CI/CD security gates
  7. 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