From c9d594a8e7bf1a4d7cdaf665d458a4c6d072efa4 Mon Sep 17 00:00:00 2001 From: dextmorgn <64375473+dextmorgn@users.noreply.github.com> Date: Sat, 30 May 2026 18:44:07 +0200 Subject: [PATCH 1/5] fix(api): load AUTH_SECRET from validated core.auth source deps.py read AUTH_SECRET via os.getenv() before load_dotenv() ran, yielding None when the secret was only present in .env. Import the already-validated AUTH_SECRET from core.auth (loads dotenv first and raises if unset) instead of re-reading it. Removes the dup + ordering bug. --- flowsint-api/app/api/deps.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/flowsint-api/app/api/deps.py b/flowsint-api/app/api/deps.py index feafcf0c..98804d37 100644 --- a/flowsint-api/app/api/deps.py +++ b/flowsint-api/app/api/deps.py @@ -2,19 +2,11 @@ from fastapi import Depends, HTTPException, status, Request from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from sqlalchemy.orm import Session -from flowsint_core.core.auth import ALGORITHM +from flowsint_core.core.auth import ALGORITHM, AUTH_SECRET from flowsint_core.core.postgre_db import get_db from flowsint_core.core.models import Profile from typing import Optional -import os -from dotenv import load_dotenv - -# Remplace avec ton URL de BDD -AUTH_SECRET = os.getenv("AUTH_SECRET") - -load_dotenv() - oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") From dd00bae9ceaae00fae1b942fbab41658a3a1ba27 Mon Sep 17 00:00:00 2001 From: dextmorgn <64375473+dextmorgn@users.noreply.github.com> Date: Sat, 30 May 2026 18:49:43 +0200 Subject: [PATCH 2/5] fix(api): run production container as non-root flowsint user The production stage created the flowsint user and chowned all app files to it, but the USER directive was commented out, so the API ran as root. Uncommented it. Verified: image builds, container reports whoami=flowsint and /health returns 200. --- flowsint-api/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flowsint-api/Dockerfile b/flowsint-api/Dockerfile index c23f8b9b..98987106 100644 --- a/flowsint-api/Dockerfile +++ b/flowsint-api/Dockerfile @@ -110,7 +110,7 @@ WORKDIR /app/flowsint-api RUN chmod +x entrypoint.sh # Switch to non-root user -# USER flowsint +USER flowsint EXPOSE 5001 From ee06d6cf3e9af71fe40d526fe9581bb8c2d4d051 Mon Sep 17 00:00:00 2001 From: dextmorgn <64375473+dextmorgn@users.noreply.github.com> Date: Sat, 30 May 2026 18:49:44 +0200 Subject: [PATCH 3/5] fix(api): restrict CORS origins via ALLOWED_ORIGINS env allow_origins was ["*"] together with allow_credentials=True, which is both insecure and rejected by browsers for credentialed requests. Read a comma-separated ALLOWED_ORIGINS env var instead, defaulting to the Vite dev origin http://localhost:5173. Documented in .env.example. --- .env.example | 2 ++ flowsint-api/app/main.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index cb62c8d9..2d6c1fe1 100644 --- a/.env.example +++ b/.env.example @@ -6,5 +6,7 @@ NEO4J_URI_BOLT=bolt://neo4j:7687 NEO4J_USERNAME=neo4j NEO4J_PASSWORD=password VITE_API_URL=http://127.0.0.1:5001 +# Comma-separated CORS allowed origins. Defaults to http://localhost:5173 (Vite dev) when unset. +ALLOWED_ORIGINS=http://localhost:5173 DATABASE_URL=postgresql://flowsint:flowsint@localhost:5433/flowsint REDIS_URL=redis://redis:6379/0 diff --git a/flowsint-api/app/main.py b/flowsint-api/app/main.py index 60c99215..e45875fe 100644 --- a/flowsint-api/app/main.py +++ b/flowsint-api/app/main.py @@ -1,3 +1,5 @@ +import os + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -16,8 +18,12 @@ from app.api.routes import types from app.api.routes import custom_types from app.api.routes import enricher_templates +# Comma-separated list of allowed origins, e.g. "https://app.example.com,https://staging.example.com" +# Falls back to localhost dev origin when unset. Never use "*" with allow_credentials=True. origins = [ - "*", + o.strip() + for o in os.getenv("ALLOWED_ORIGINS", "http://localhost:5173").split(",") + if o.strip() ] From 1d409d7929b4c777fdf4ffe23c90cacefc0317a3 Mon Sep 17 00:00:00 2001 From: dextmorgn <64375473+dextmorgn@users.noreply.github.com> Date: Sat, 30 May 2026 19:02:14 +0200 Subject: [PATCH 4/5] fix(scan): persist results to existing details column, errors to error column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Scan model exposes details (JSON) and error (Text) columns, but the enricher/flow tasks assigned to scan.results — a non-mapped attribute. SQLAlchemy accepted the assignment silently, so commit() never persisted it and the data was lost. Success paths now write scan.details; failure paths write scan.error. ScanCreate.results renamed to details to match. Scan repository tests pass. --- flowsint-api/app/api/schemas/scan.py | 2 +- flowsint-core/src/flowsint_core/tasks/enricher.py | 12 ++++++------ flowsint-core/src/flowsint_core/tasks/flow.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/flowsint-api/app/api/schemas/scan.py b/flowsint-api/app/api/schemas/scan.py index b39d4aa1..f1e70f9b 100644 --- a/flowsint-api/app/api/schemas/scan.py +++ b/flowsint-api/app/api/schemas/scan.py @@ -8,7 +8,7 @@ class ScanCreate(BaseModel): values: Optional[List[str]] = None sketch_id: Optional[UUID4] = None status: Optional[str] = None - results: Optional[Any] = None + details: Optional[Any] = None class ScanRead(ORMBase): diff --git a/flowsint-core/src/flowsint_core/tasks/enricher.py b/flowsint-core/src/flowsint_core/tasks/enricher.py index f08ede65..1238f057 100644 --- a/flowsint-core/src/flowsint_core/tasks/enricher.py +++ b/flowsint-core/src/flowsint_core/tasks/enricher.py @@ -69,10 +69,10 @@ def run_enricher( results = asyncio.run(enricher.execute(values=serialized_objects)) scan.status = EventLevel.COMPLETED - scan.results = to_json_serializable(results) + scan.details = to_json_serializable(results) session.commit() - return {"result": scan.results} + return {"result": scan.details} except Exception as ex: session.rollback() @@ -82,7 +82,7 @@ def run_enricher( scan = session.query(Scan).filter(Scan.id == uuid.UUID(self.request.id)).first() if scan: scan.status = EventLevel.FAILED - scan.results = {"error": error_logs} + scan.error = error_logs session.commit() self.update_state(state=states.FAILURE) @@ -145,10 +145,10 @@ def run_template_enricher( results = asyncio.run(enricher.execute(values=serialized_objects)) scan.status = EventLevel.COMPLETED - scan.results = to_json_serializable(results) + scan.details = to_json_serializable(results) session.commit() - return {"result": scan.results} + return {"result": scan.details} except Exception as ex: session.rollback() @@ -162,7 +162,7 @@ def run_template_enricher( ) if scan: scan.status = EventLevel.FAILED - scan.results = {"error": error_logs} + scan.error = error_logs session.commit() self.update_state(state=states.FAILURE) diff --git a/flowsint-core/src/flowsint_core/tasks/flow.py b/flowsint-core/src/flowsint_core/tasks/flow.py index ebce0256..25cc2c46 100644 --- a/flowsint-core/src/flowsint_core/tasks/flow.py +++ b/flowsint-core/src/flowsint_core/tasks/flow.py @@ -65,10 +65,10 @@ def run_flow( results = enricher.scan(values=serialized_objects) scan.status = EventLevel.COMPLETED - scan.results = to_json_serializable(results) + scan.details = to_json_serializable(results) session.commit() - return {"result": scan.results} + return {"result": scan.details} except Exception as ex: session.rollback() @@ -78,7 +78,7 @@ def run_flow( scan = session.query(Scan).filter(Scan.id == uuid.UUID(self.request.id)).first() if scan: scan.status = EventLevel.FAILED - scan.results = {"error": error_logs} + scan.error = error_logs session.commit() self.update_state(state=states.FAILURE) From e8ca7b39bf7c73debfe5f29ccb3ea8446b85f424 Mon Sep 17 00:00:00 2001 From: dextmorgn <64375473+dextmorgn@users.noreply.github.com> Date: Sat, 30 May 2026 19:03:30 +0200 Subject: [PATCH 5/5] fix(types): apply extra=allow via Pydantic v2 model_config FlowsintType declared an inner `class ConfigDict: extra = "allow"`, which Pydantic v2 treats as an ordinary nested class and ignores entirely. The base therefore fell back to the default extra="ignore", silently dropping user-supplied extra properties on every entity type. Replaced with model_config = ConfigDict(extra="allow"). Extra keys are now retained. flowsint-types (44), flowsint-enrichers (12) and flowsint-core (390) suites pass. --- flowsint-types/src/flowsint_types/flowsint_base.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/flowsint-types/src/flowsint_types/flowsint_base.py b/flowsint-types/src/flowsint_types/flowsint_base.py index 51b3b14a..6e4eea4a 100644 --- a/flowsint-types/src/flowsint_types/flowsint_base.py +++ b/flowsint-types/src/flowsint_types/flowsint_base.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field class FlowsintType(BaseModel): @@ -24,6 +24,5 @@ class FlowsintType(BaseModel): title="Label", ) - # Allow extra keys to support for additional properties from user - class ConfigDict: - extra = "allow" + # Allow extra keys to support additional properties from the user + model_config = ConfigDict(extra="allow")