diff --git a/CMakeLists.txt b/CMakeLists.txt
index f8dbe375e217020a4c4570bd67c1b466e6593130..49a68a4d86c5797337a543a4169145006c3cbdec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,16 +66,36 @@ endif()
 
 target_compile_features(${module_name} PRIVATE cxx_std_14)
 
+if (DOSANITIZE STREQUAL "ON")
+set(SANITIZE_FLAGS -fsanitize=address,leak,undefined,float-divide-by-zero -fno-omit-frame-pointer)
+#TODO sanitizer seems buggy in some situations with msvc, leading to linker errors, temporarily inactivating it
+#set(SANITIZE_MSVC_FLAGS)
+set(SANITIZE_MSVC_FLAGS /fsanitize=address)
+target_compile_definitions(${module_name} PUBLIC _DISABLE_VECTOR_ANNOTATION)
+else()
+set(SANITIZE_FLAGS)
+set(SANITIZE_MSVC_FLAGS)
+endif()
+
+set(STRICT_ALIASING_FLAGS -fstrict-aliasing -Wstrict-aliasing=2)
+
 # -fvisibility=hidden required by pybind11
 target_compile_options(${module_name} PUBLIC
     $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
     -fvisibility=hidden>)
 target_compile_options(${module_name} PRIVATE
-    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-    -Wall -Wextra -Wold-style-cast -Winline -pedantic -Werror=narrowing -Wshadow $<$<BOOL:${WERROR}>:-Werror>>)
+$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
+-Wall -Wextra -Wold-style-cast -Winline -pedantic -Werror=narrowing -Wshadow $<$<BOOL:${WERROR}>:-Werror> ${SANITIZE_FLAGS}>)
+target_compile_options(${module_name} PRIVATE
+$<$<CXX_COMPILER_ID:GNU>:${STRICT_ALIASING_FLAGS}>)
 target_compile_options(${module_name} PRIVATE
-    $<$<CXX_COMPILER_ID:MSVC>:
-    /W4>)
+$<$<CXX_COMPILER_ID:MSVC>:
+/W4 /wd4477 /DWIN32 /D_WINDOWS /GR /EHsc /MP /Zc:__cplusplus /Zc:preprocessor /permissive- ${SANITIZE_MSVC_FLAGS}>)
+# TODO FIXME: I'm not sure it's a good idea to propagate this option but, at this point, it was the only way that worked to silence C4477
+target_compile_options(${module_name} PUBLIC $<$<CXX_COMPILER_ID:MSVC>: /wd4477>)
+
+target_link_options(${module_name} PUBLIC $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:${SANITIZE_FLAGS}>)
+#target_link_options(${module_name} PUBLIC $<$<CXX_COMPILER_ID:MSVC>:${SANITIZE_MSVC_FLAGS}>)
 
 if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE)
     append_coverage_compiler_flags()
diff --git a/setup.ps1 b/setup.ps1
index 61324cf4a7d64094f5ead498adf64719c3290f06..9a110380b91b06cbb45017612e1350fb3bca49c6 100644
--- a/setup.ps1
+++ b/setup.ps1
@@ -40,7 +40,7 @@ if ($install_reqs)
 mkdir -Force build_cpp
 mkdir -Force $env:AIDGE_INSTALL_PATH
 Set-Location build_cpp
-cmake -DCMAKE_INSTALL_PREFIX:PATH=$env:AIDGE_INSTALL_PATH -DCMAKE_BUILD_TYPE=Debug ..
+cmake -DCMAKE_INSTALL_PREFIX:PATH=$env:AIDGE_INSTALL_PATH -DCMAKE_BUILD_TYPE=Debug -DDOSANITIZE=OFF ..
 if(!$?) { $lastError = $LASTEXITCODE; Set-Location $PSScriptRoot; Exit $lastError }
 cmake --build . -j2
 if(!$?) { $lastError = $LASTEXITCODE; Set-Location $PSScriptRoot; Exit $lastError }
diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt
index 5ccfa3832a8ce2522f18ab07e11a78cf8b462a40..f2c446701a45736b10b62d015be442c7ed46a615 100644
--- a/unit_tests/CMakeLists.txt
+++ b/unit_tests/CMakeLists.txt
@@ -14,6 +14,44 @@ file(GLOB_RECURSE src_files "*.cpp")
 
 add_executable(tests${module_name} ${src_files})
 
+target_compile_features(tests${module_name} PRIVATE cxx_std_14)
+
+set(FORCE_CI TRUE)
+if (NOT(FORCE_CI))
+
+if (DOSANITIZE STREQUAL "ON")
+set(SANITIZE_FLAGS -fsanitize=address,leak,undefined,float-divide-by-zero -fno-omit-frame-pointer)
+#TODO sanitizer seems buggy in some situations with msvc, leading to linker errors, temporarily inactivating it
+#set(SANITIZE_MSVC_FLAGS)
+set(SANITIZE_MSVC_FLAGS /fsanitize=address)
+target_compile_definitions(tests${module_name} PUBLIC _DISABLE_VECTOR_ANNOTATION)
+else()
+set(SANITIZE_FLAGS)
+set(SANITIZE_MSVC_FLAGS)
+endif()
+
+set(STRICT_ALIASING_FLAGS -fstrict-aliasing -Wstrict-aliasing=2)
+
+# -fvisibility=hidden required by pybind11
+target_compile_options(tests${module_name} PUBLIC
+    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
+    -fvisibility=hidden>)
+target_compile_options(tests${module_name} PRIVATE
+$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
+-Wall -Wextra -Wold-style-cast -Winline -pedantic -Werror=narrowing -Wshadow $<$<BOOL:${WERROR}>:-Werror> ${SANITIZE_FLAGS}>)
+target_compile_options(tests${module_name} PRIVATE
+$<$<CXX_COMPILER_ID:GNU>:${STRICT_ALIASING_FLAGS}>)
+target_compile_options(${module_name} PRIVATE
+$<$<CXX_COMPILER_ID:MSVC>:
+/W4 /DWIN32 /D_WINDOWS /GR /EHsc /MP /Zc:__cplusplus /Zc:preprocessor /permissive- ${SANITIZE_MSVC_FLAGS}>)
+# TODO FIXME: I'm not sure it's a good idea to propagate this option but, at this point, it was the only way that worked to silence C4477
+target_compile_options(${module_name} PUBLIC $<$<CXX_COMPILER_ID:MSVC>: /wd4477>)
+
+target_link_options(tests${module_name} PUBLIC $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:${SANITIZE_FLAGS}>)
+#target_link_options(tests${module_name} PUBLIC $<$<CXX_COMPILER_ID:MSVC>:${SANITIZE_MSVC_FLAGS}>)
+
+endif()
+
 target_link_libraries(tests${module_name} PUBLIC ${module_name})
 
 target_link_libraries(tests${module_name} PRIVATE Catch2::Catch2WithMain)