21 KiB
🔒 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:
# 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: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).
- Lista alla API-endpoints (särskilt de som hanterar användardata:
🚨 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ändreq.user.idför att jämföra medresource.userId. Exempel:// 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<string>('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.tseller använd den på specifika routes med@UseGuards(OwnershipGuard)."
Uppgift för Copilot:
"Uppdatera alla controller-metoder som hanterar
Recipe,Inventory, ellerUserför att använda@UseGuards(OwnershipGuard)och@SetMetadata('resource', 'recipe')(eller motsvarande). Exempel:// 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.idtill allafindMany,findFirst,update, ochdelete-förfrågningar för tabellernaRecipeochInventory. Exempel:// 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
OwnershipGuardför att sättaprismaService.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:
- En användare kan hämta sina egna recept/inventory.
- En användare kan inte hämta andras recept/inventory.
Exempel:
// 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
findManyförRecipe,Inventory, ellerUserför att alltid inkluderawhere: { userId: req.user.id }. Exempel:// 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
recipesochinventorysom automatiskt filtrerar påuserId. Exempel:-- 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
userIdsom parameter. Exempel: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:
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 <token>).- Validering av svar (kontrollera att
userIdi svaret matchar den inloggade användaren).- Felhantering för 403 (Forbidden) och 401 (Unauthorized).
Exempel:// 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<Map<String, dynamic>> 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<int> _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:// lib/services/secure_storage.dart import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class SecureStorage { final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future<void> saveToken(String token) async { await _storage.write(key: 'auth_token', value: token); } Future<String?> getToken() async { return await _storage.read(key: 'auth_token'); } Future<void> 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:
// 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ändcryptoför att verifiera HMAC-SHA256-signaturen. Exempel:// 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_SECRETi.env.exampleoch dokumentera att den måste sättas i produktion. Exempel:# .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 .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.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
Dockerfileochdocker-compose.ymlför att:
- Ta bort alla
ENV-instruktioner som exponerar känsliga data.- Använd
docker run --env-fileeller Docker Secrets för känsliga variabler.Exempel:
# 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:# 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:// 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<string> { const encrypted = await openpgp.encrypt({ message: openpgp.Message.fromText(privateKey), passwords: [password], }); return encrypted.toString(); } async decryptPrivateKey(encryptedPrivateKey: string, password: string): Promise<string> { 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
encryptedPrivateKeyistället förprivateKeyi klart text. Exempel: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.tsför att lägga till säkerhetsheaders. Exempel:// 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:
# .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:
# .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:
- En lista över alla säkerhetsåtgärder som vidtagits.
- Instruktioner för hur man rapporterar säkerhetshål.
- En checklist för säkerhetsgranskning före deployment.
Exempel:
# 🔒 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
- IDOR-skydd (CRITICAL)
- Full Table Dump-skydd (CRITICAL)
- Flutter-säkerhet (HIGH)
- Webhook-säkerhet (HIGH)
- Hemligheter och kryptering (HIGH)
- Säkerhetsheaders (MEDIUM)
- 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
- Börja med IDOR-skyddet (steg 1.1–1.3).
- Gå vidare till Full Table Dump-skydd (steg 2.1–2.3).
- Implementera Flutter-säkerhet (steg 3.1–3.3).
- Säkra webhooks (steg 4.1–4.2).
- Säkra hemligheter (steg 5.1–5.3).
- Lägg till säkerhetsheaders (steg 6.1).
- Automatisera tester (steg 7.1–7.2).
- 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?