# 🔒 SĂ€kerhetshĂ€rdningsplan för Recipe-App (Flutter + NestJS + MariaDB) **MĂ„l:** TĂ€ppa till IDOR, Full Table Dump, och andra kritiska sĂ€kerhetshĂ„l i **backend (NestJS/Prisma/MariaDB)**, **Flutter-frontend**, och **infrastruktur (Docker/Gitea/Ubuntu)**. **Prioritet:** CRITICAL → HIGH → MEDIUM **Tidsuppskattning:** 1–3 dagar --- --- ## 📌 **0. Förberedelser** ### 0.1. Miljö och verktyg - **Installera sĂ€kerhetsverktyg:** ```bash # Scanna efter lĂ€ckta hemligheter i Git npm install -g gitleaks gitleaks detect --source . --report-path gitleaks-report.json # Scanna Docker-containers docker scan --file Dockerfile ``` - **Skapa en `security-audit`-gren:** ```bash git checkout -b security-audit-$(date +%Y%m%d) ``` - **Dokumentera nuvarande sĂ€kerhetsstatus:** - Lista alla **API-endpoints** (sĂ€rskilt de som hanterar anvĂ€ndardata: `recipes`, `inventory`, `users`). - Lista alla **databastabeller** och deras innehĂ„ll. - Lista alla **webhooks** (t.ex. Gitea). --- --- ## 🚹 **1. IDOR (Insecure Direct Object Reference) – CRITICAL** **MĂ„l:** Se till att anvĂ€ndare endast kan komma Ă„t sina egna resurser via API:er. ### 1.1. Backend (NestJS/Prisma) #### **Steg 1: LĂ€gg till Ă€garskapskontroll i alla endpoints** **Uppgift för Copilot:** > "Skapa en **NestJS-guard** som automatiskt kontrollerar att den inloggade anvĂ€ndaren Ă€ger resursen (t.ex. `Recipe`, `Inventory`). AnvĂ€nd `req.user.id` för att jĂ€mföra med `resource.userId`. Exempel: > > ```typescript > // src/guards/ownership.guard.ts > import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; > import { Reflector } from '@nestjs/core'; > > @Injectable() > export class OwnershipGuard implements CanActivate { > constructor(private reflector: Reflector) {} > > canActivate(context: ExecutionContext): boolean { > const requiredResource = this.reflector.get('resource', context.getHandler()); > if (!requiredResource) return true; // Ingen resurs specificerad = ingen kontroll > > const request = context.switchToHttp().getRequest(); > const resourceId = request.params.id; > const userId = request.user.id; > > // HĂ€mta resursen och kontrollera Ă€garskap > // (Anta att vi har en service för detta) > const resource = await request[requiredResource + 'Service'].findOne(resourceId); > if (!resource || resource.userId !== userId) { > throw new ForbiddenException('Du har inte tillgĂ„ng till denna resurs.'); > } > return true; > } > } > ``` > > Registrera guarden globalt i `app.module.ts` eller anvĂ€nd den pĂ„ specifika routes med `@UseGuards(OwnershipGuard)`." **Uppgift för Copilot:** > "Uppdatera alla **controller-metoder** som hanterar `Recipe`, `Inventory`, eller `User` för att anvĂ€nda `@UseGuards(OwnershipGuard)` och `@SetMetadata('resource', 'recipe')` (eller motsvarande). Exempel: > > ```typescript > // src/recipes/recipes.controller.ts > import { Controller, Get, Param, UseGuards, SetMetadata } from '@nestjs/common'; > import { OwnershipGuard } from '../guards/ownership.guard'; > > @Controller('recipes') > @UseGuards(OwnershipGuard) > export class RecipesController { > @Get(':id') > @SetMetadata('resource', 'recipe') > async getRecipe(@Param('id') id: number) { > return this.recipesService.findOne(id); > } > } > ```" > ``` #### **Steg 2: Prisma-middleware för automatisk filtrering** **Uppgift för Copilot:** > "Skapa en **Prisma-middleware** som automatiskt lĂ€gger till `userId: req.user.id` till alla `findMany`, `findFirst`, `update`, och `delete`-förfrĂ„gningar för tabellerna `Recipe` och `Inventory`. Exempel: > > ```typescript > // src/prisma/prisma.service.ts > import { Injectable, OnModuleInit } from '@nestjs/common'; > import { PrismaClient } from '@prisma/client'; > > @Injectable() > export class PrismaService extends PrismaClient implements OnModuleInit { > private userId: number; > > setUserId(userId: number) { > this.userId = userId; > } > > async onModuleInit() { > await this.$connect(); > this.$use(async (params, next) => { > if (this.userId && ['Recipe', 'Inventory'].includes(params.model)) { > params.where = { ...params.where, userId: this.userId }; > } > return next(params); > }); > } > } > ``` > > Uppdatera sedan `OwnershipGuard` för att sĂ€tta `prismaService.setUserId(req.user.id)`." #### **Steg 3: Testa för IDOR** **Uppgift för Copilot:** > "Skapa **Jest-tester** för att verifiera att IDOR-skyddet fungerar. Testa: > > 1. En anvĂ€ndare kan hĂ€mta sina egna recept/inventory. > 2. En anvĂ€ndare **kan inte** hĂ€mta andras recept/inventory. > > Exempel: > > ```typescript > // test/idor.test.ts > describe('IDOR Protection', () => { > let app; > let user1, user2, recipe1; > > beforeAll(async () => { > app = await createTestApp(); > user1 = await createTestUser(); > user2 = await createTestUser(); > recipe1 = await createTestRecipe(user1.id); > }); > > it('should allow user to access their own recipe', async () => { > const response = await request(app.getHttpServer()) > .get(`/recipes/${recipe1.id}`) > .set('Authorization', `Bearer ${user1.token}`); > expect(response.status).toBe(200); > }); > > it('should block user from accessing another user\'s recipe', async () => { > const response = await request(app.getHttpServer()) > .get(`/recipes/${recipe1.id}`) > .set('Authorization', `Bearer ${user2.token}`); > expect(response.status).toBe(403); > }); > }); > ```" > ``` --- --- ## đŸ—ƒïž **2. Full Table Dump – CRITICAL** **MĂ„l:** Förhindra att anvĂ€ndare kan hĂ€mta alla rader frĂ„n en tabell. ### 2.1. Backend (Prisma/NestJS) **Uppgift för Copilot:** > "Uppdatera alla **Prisma-förfrĂ„gningar** som anvĂ€nder `findMany` för `Recipe`, `Inventory`, eller `User` för att **alltid** inkludera `where: { userId: req.user.id }`. Exempel: > > ```typescript > // src/recipes/recipes.service.ts > async findAll(userId: number) { > return this.prisma.recipe.findMany({ > where: { userId } // Tvinga filtrering > }); > } > ```" > ``` ### 2.2. MariaDB (RLS-liknande kontroll) **Uppgift för Copilot:** > "Skapa **MariaDB-vyer** för `recipes` och `inventory` som automatiskt filtrerar pĂ„ `userId`. Exempel: > > ```sql > -- Skapa en vy för anvĂ€ndarens recept > CREATE VIEW user_recipes AS > SELECT * FROM recipes WHERE userId = @current_user_id; > > -- Skapa en stored procedure för att sĂ€tta @current_user_id > DELIMITER // > CREATE PROCEDURE SetCurrentUserId(IN p_userId INT) > BEGIN > SET @current_user_id = p_userId; > END // > DELIMITER ; > ``` > > **Obs:** MariaDB saknar inbyggt RLS, sĂ„ vyer + stored procedures Ă€r det bĂ€sta alternativet. Dokumentera att alla förfrĂ„gningar ska gĂ„ via vyer." **Uppgift för Copilot:** > "Skapa en **stored procedure** för att hĂ€mta anvĂ€ndarens inventory, med `userId` som parameter. Exempel: > > ```sql > DELIMITER // > CREATE PROCEDURE GetUserInventory(IN p_userId INT) > BEGIN > SELECT * FROM inventory WHERE userId = p_userId; > END // > DELIMITER ; > ```" > ``` ### 2.3. Testa för Full Table Dump **Uppgift för Copilot:** > "Skapa **Jest-tester** för att verifiera att anvĂ€ndare inte kan hĂ€mta alla rader. Exempel: > > ```typescript > it('should block full table dump for recipes', async () => { > const user = await createTestUser(); > const response = await request(app.getHttpServer()) > .get('/recipes') > .set('Authorization', `Bearer ${user.token}`); > expect(response.body.length).toBeLessThanOrEqual(10); // Anta att anvĂ€ndaren har <10 recept > }); > ```" > ``` --- --- ## đŸ“± **3. Flutter-SĂ€kerhet – HIGH** **MĂ„l:** SĂ€kra kommunikation mellan Flutter och backend, samt hantering av kĂ€nsliga data. ### 3.1. API-Anrop **Uppgift för Copilot:** > "Skapa en **Dart-klass** för sĂ€kra API-anrop till backend, med: > > - **JWT-autentisering** (skicka `Authorization: Bearer `). > - **Validering av svar** (kontrollera att `userId` i svaret matchar den inloggade anvĂ€ndaren). > - **Felhantering** för 403 (Forbidden) och 401 (Unauthorized). > Exempel: > > ```dart > // lib/services/api_service.dart > import 'package:http/http.dart' as http; > import 'dart:convert'; > > class ApiService { > final String baseUrl; > final String token; > > ApiService({required this.baseUrl, required this.token}); > > Future> getRecipe(int recipeId) async { > final response = await http.get( > Uri.parse('$baseUrl/recipes/$recipeId'), > headers: {'Authorization': 'Bearer $token'}, > ); > > if (response.statusCode == 403) { > throw Exception('Du har inte tillgĂ„ng till detta recept.'); > } else if (response.statusCode != 200) { > throw Exception('Fel vid hĂ€mtning av recept.'); > } > > final data = json.decode(response.body); > // Kontrollera att anvĂ€ndar-ID matchar (om backend skickar med det) > if (data['userId'] != await _getCurrentUserId()) { > throw Exception('Ogiltig anvĂ€ndare för detta recept.'); > } > return data; > } > > Future _getCurrentUserId() async { > // HĂ€mta den inloggade anvĂ€ndarens ID frĂ„n lokal lagring > final user = await _getStoredUser(); > return user['id']; > } > } > ```" > ``` ### 3.2. Lagring av kĂ€nsliga data **Uppgift för Copilot:** > "Skapa en **sĂ€ker lagringslösning** för JWT-tokens och anvĂ€ndardata i Flutter med `flutter_secure_storage`. Exempel: > > ```dart > // lib/services/secure_storage.dart > import 'package:flutter_secure_storage/flutter_secure_storage.dart'; > > class SecureStorage { > final FlutterSecureStorage _storage = const FlutterSecureStorage(); > > Future saveToken(String token) async { > await _storage.write(key: 'auth_token', value: token); > } > > Future getToken() async { > return await _storage.read(key: 'auth_token'); > } > > Future deleteToken() async { > await _storage.delete(key: 'auth_token'); > } > } > ```" > ``` ### 3.3. Inputvalidering **Uppgift för Copilot:** > "Skapa en **valideringsklass** för all input i Flutter (t.ex. för recept-ID, anvĂ€ndar-ID). Exempel: > > ```dart > // lib/utils/validators.dart > class Validators { > static bool isValidId(String id) { > return RegExp(r'^[0-9]+$').hasMatch(id); > } > > static bool isValidUuid(String uuid) { > return RegExp(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$').hasMatch(uuid); > } > } > ```" > ``` --- --- ## 🔌 **4. Webhook-SĂ€kerhet – HIGH** **MĂ„l:** Validera signaturer för alla inkommande webhooks (t.ex. Gitea). ### 4.1. Gitea-Webhooks **Uppgift för Copilot:** > "Skapa en **NestJS-controller** för Gitea-webhooks som validerar `X-Gitea-Signature`-headern. AnvĂ€nd `crypto` för att verifiera HMAC-SHA256-signaturen. Exempel: > > ```typescript > // src/webhooks/gitea.controller.ts > import { Controller, Post, Headers, Body, UnauthorizedException } from '@nestjs/common'; > import * as crypto from 'crypto'; > > @Controller('webhooks/gitea') > export class GiteaController { > @Post() > async handleWebhook( > @Headers('X-Gitea-Signature') signature: string, > @Body() body: any, > ) { > const secret = process.env.GITEA_WEBHOOK_SECRET; > const hmac = crypto.createHmac('sha256', secret); > hmac.update(JSON.stringify(body)); > const expectedSignature = `sha256=${hmac.digest('hex')}`; > > if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) { > throw new UnauthorizedException('Ogiltig signatur'); > } > > // Hantera webhook-hĂ€ndelsen (t.ex. triggera backup) > return { status: 'success' }; > } > } > ```" > ``` ### 4.2. Miljövariabler **Uppgift för Copilot:** > "LĂ€gg till `GITEA_WEBHOOK_SECRET` i `.env.example` och dokumentera att den **mĂ„ste** sĂ€ttas i produktion. Exempel: > > ```env > # .env.example > GITEA_WEBHOOK_SECRET=your_gitea_webhook_secret_here > ```" > ``` --- --- ## 🔑 **5. Hemligheter och Kryptering – HIGH** **MĂ„l:** Skydda API-nycklar, databaslösenord, och krypteringsnycklar. ### 5.1. `.env`-filer **Uppgift för Copilot:** > "Skapa en `**.gitignore`-fil** som utesluter alla `.env`-filer och kĂ€nsliga konfigurationsfiler. Exempel: > > ```gitignore > # .gitignore > .env > .env.* > !.env.example > *.pem > *.key > ```" > ``` **Uppgift för Copilot:** > "Skapa en `**.env.example`-fil** med alla nödvĂ€ndiga miljövariabler (utan vĂ€rden). Exempel: > > ```env > # .env.example > DATABASE_URL=mysql://user:password@localhost:3306/recipe_app > JWT_SECRET=your_jwt_secret_here > GITEA_WEBHOOK_SECRET=your_gitea_webhook_secret_here > PGP_PRIVATE_KEY_ENCRYPTION_PASSWORD=your_password_here > ```" > ``` ### 5.2. Docker **Uppgift för Copilot:** > "Uppdatera `Dockerfile` och `docker-compose.yml` för att: > > 1. **Ta bort alla `ENV`-instruktioner** som exponerar kĂ€nsliga data. > 2. AnvĂ€nd `docker run --env-file` eller Docker Secrets för kĂ€nsliga variabler. > > Exempel: > > ```yaml > # docker-compose.yml > services: > app: > build: . > env_file: > - .env > ```" > ``` **Uppgift för Copilot:** > "Skapa en `**docker-compose.override.yml**` för lokal utveckling, som aldrig pushas till Git. Exempel: > > ```yaml > # docker-compose.override.yml > services: > app: > environment: > - NODE_ENV=development > - DATABASE_URL=mysql://dev_user:dev_password@db:3306/recipe_app > ```" > ``` ### 5.3. PGP/AES-Kryptering **Uppgift för Copilot:** > "Skapa en **tjĂ€nst för kryptering/avkryptering** av PGP-nycklar med `openpgp`. Exempel: > > ```typescript > // src/crypto/crypto.service.ts > import { Injectable } from '@nestjs/common'; > import * as openpgp from 'openpgp'; > > @Injectable() > export class CryptoService { > async encryptPrivateKey(privateKey: string, password: string): Promise { > const encrypted = await openpgp.encrypt({ > message: openpgp.Message.fromText(privateKey), > passwords: [password], > }); > return encrypted.toString(); > } > > async decryptPrivateKey(encryptedPrivateKey: string, password: string): Promise { > const message = await openpgp.readMessage({ armoredMessage: encryptedPrivateKey }); > const { data: decrypted } = await openpgp.decrypt({ > message, > passwords: [password], > }); > return decrypted.toString(); > } > } > ```" > ``` **Uppgift för Copilot:** > "Uppdatera **Prisma-schemat** för att lagra `encryptedPrivateKey` istĂ€llet för `privateKey` i klart text. Exempel: > > ```prisma > model User { > id Int @id @default(autoincrement()) > encryptedPrivateKey String // Krypterad PGP-privat nyckel > // ... > } > ```" > ``` --- --- ## đŸ›Ąïž **6. SĂ€kerhetsheaders och CSP – MEDIUM** **MĂ„l:** Skydda backend med sĂ€kerhetsheaders. ### 6.1. NestJS (Backend) **Uppgift för Copilot:** > "Konfigurera **Helmet** i `main.ts` för att lĂ€gga till sĂ€kerhetsheaders. Exempel: > > ```typescript > // src/main.ts > import { NestFactory } from '@nestjs/core'; > import { AppModule } from './app.module'; > import * as helmet from 'helmet'; > > async function bootstrap() { > const app = await NestFactory.create(AppModule); > app.use(helmet()); > await app.listen(3000); > } > bootstrap(); > ```" > ``` --- --- ## đŸ§Ș **7. Automatiserade SĂ€kerhetstester – MEDIUM** **MĂ„l:** Integrera sĂ€kerhetstester i CI/CD. ### 7.1. GitHub Actions **Uppgift för Copilot:** > "Skapa en **GitHub Actions-workflow** för sĂ€kerhetskontroller. Exempel: > > ```yaml > # .github/workflows/security-audit.yml > name: Security Audit > on: [push, pull_request] > > jobs: > gitleaks: > runs-on: ubuntu-latest > steps: > - uses: actions/checkout@v4 > - uses: gitleaks/gitleaks-action@v2 > env: > GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} > > docker-scan: > runs-on: ubuntu-latest > steps: > - uses: actions/checkout@v4 > - name: Build Docker image > run: docker build -t recipe-app . > - name: Scan for vulnerabilities > run: docker scan recipe-app > ```" > ``` ### 7.2. OWASP ZAP **Uppgift för Copilot:** > "Skapa en **GitHub Actions-workflow** för OWASP ZAP-scanning. Exempel: > > ```yaml > # .github/workflows/zap-scan.yml > name: OWASP ZAP Scan > on: [push] > > jobs: > zap_scan: > runs-on: ubuntu-latest > steps: > - uses: actions/checkout@v4 > - name: Start app > run: | > docker-compose up -d > sleep 30 # VĂ€nta pĂ„ att appen ska starta > - name: Run OWASP ZAP > uses: zaproxy/action-full-scan@v0.4.0 > with: > target: 'http://localhost:3000' > ```" > ``` --- --- ## 📝 **8. Dokumentation** **Uppgift för Copilot:** > "Skapa en `**SECURITY.md**`-fil i rotkatalogen med: > > 1. En lista över alla sĂ€kerhetsĂ„tgĂ€rder som vidtagits. > 2. Instruktioner för hur man rapporterar sĂ€kerhetshĂ„l. > 3. En checklist för sĂ€kerhetsgranskning före deployment. > > Exempel: > > ```markdown > # 🔒 SĂ€kerhetsdokumentation > > ## Vidtagna Ă„tgĂ€rder > - [x] IDOR-skydd för alla API-endpoints > - [x] Full Table Dump-skydd via Prisma-middleware > - [x] Webhook-signaturvalidering för Gitea > - [x] `.env`-filer uteslutna frĂ„n Git > - [x] Flutter: SĂ€ker lagring av JWT-tokens > > ## Rapportera sĂ€kerhetshĂ„l > Skicka ett e-post till security@recipe-app.com. > > ## Checklist före deployment > - [ ] Alla `.env`-filer Ă€r uteslutna frĂ„n Git. > - [ ] Alla API-endpoints har Ă€garskapskontroll. > - [ ] Webhook-signaturer valideras. > - [ ] Docker-containers Ă€r scannade för sĂ„rbarheter. > - [ ] Flutter-app anvĂ€nder `flutter_secure_storage` för tokens. > ```" > ``` --- --- ## ✅ **9. Avslutande Checklista** | **ÅtgĂ€rd** | **Status** | **AnsvarsomrĂ„de** | | -------------------------------- | ---------- | ----------------------- | | IDOR-skydd i alla endpoints | ⬜ | Backend (NestJS/Prisma) | | Full Table Dump-skydd | ⬜ | Backend (Prisma) | | Flutter: SĂ€kra API-anrop | ⬜ | Flutter | | Flutter: SĂ€ker lagring av tokens | ⬜ | Flutter | | Webhook-signaturvalidering | ⬜ | Backend (NestJS) | | `.env`-filer skyddade | ⬜ | Git/Docker | | Docker-sĂ€kerhet | ⬜ | Docker | | PGP-nycklar krypterade | ⬜ | Backend (CryptoService) | | SĂ€kerhetsheaders (Helmet) | ⬜ | Backend (NestJS) | | Automatiserade sĂ€kerhetstester | ⬜ | CI/CD (GitHub Actions) | | SĂ€kerhetsdokumentation | ⬜ | Dokumentation | --- --- ## 🎯 **10. Prioriterad Ordning för Implementering** 1. **IDOR-skydd** (CRITICAL) 2. **Full Table Dump-skydd** (CRITICAL) 3. **Flutter-sĂ€kerhet** (HIGH) 4. **Webhook-sĂ€kerhet** (HIGH) 5. **Hemligheter och kryptering** (HIGH) 6. **SĂ€kerhetsheaders** (MEDIUM) 7. **Automatiserade tester** (MEDIUM) --- --- ## 💡 **11. Tips för GitHub Copilot** - **Var specifik:** Beskriv exakt vad du vill uppnĂ„ (t.ex. "Skapa en NestJS-guard för Ă€garskapskontroll"). - **Ge exempel:** Inkludera kodsnuttar för att visa vad du menar. - **Be om tester:** FrĂ„ga Copilot att skapa **Jest-tester** för varje sĂ€kerhetsĂ„tgĂ€rd. - **Iterera:** Om svaret inte Ă€r perfekt, be Copilot att **förbĂ€ttra** eller **förtydliga** koden. --- --- ## 🚀 **12. NĂ€sta Steg** 1. **Börja med IDOR-skyddet** (steg 1.1–1.3). 2. **GĂ„ vidare till Full Table Dump-skydd** (steg 2.1–2.3). 3. **Implementera Flutter-sĂ€kerhet** (steg 3.1–3.3). 4. **SĂ€kra webhooks** (steg 4.1–4.2). 5. **SĂ€kra hemligheter** (steg 5.1–5.3). 6. **LĂ€gg till sĂ€kerhetsheaders** (steg 6.1). 7. **Automatisera tester** (steg 7.1–7.2). 8. **Dokumentera** (steg 8). --- **FrĂ„ga till dig:** Vill du att jag **anpassar planen ytterligare** för ett specifikt omrĂ„de (t.ex. endast backend eller endast Flutter)? Eller ska vi börja med **IDOR-skyddet** först?