diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e6d9977..754dbc0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,6 +44,10 @@ jobs:
     - uses: actions/checkout@v2
       with:
         submodules: 'true'
+    - name: Enable ASan+UBSan Sanitizers
+      if: matrix.build-type == 'Debug'
+      run: |
+        echo "SANITIZER_FLAG=-DPEPARSE_USE_SANITIZER=Address,Undefined" >> $GITHUB_ENV
     - name: build
       env:
         CC: ${{ matrix.compiler.CC }}
@@ -55,6 +59,7 @@ jobs:
           -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
           -DBUILD_SHARED_LIBS=${{ matrix.build-shared }} \
           -DPEPARSE_ENABLE_TESTING=ON \
+          ${SANITIZER_FLAG} \
           ..
         cmake --build .
     - name: test
diff --git a/README.md b/README.md
index 336f97a..b93a568 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,20 @@ You can build the (catch2-based) tests by adding `-DPEPARSE_ENABLE_TESTING=ON` d
 
 To run the full test suite with the [Corkami test suite](https://github.com/corkami/pocs/tree/master/PE), you must clone the submodule with `git submodule update --init`.
 
+## Building with Sanitizers
+
+If you are familiar with C++ sanitizers and any specific development environment requirements for them (compiler, instrumented standard library, etc.), you can choose to compile with any of the following sanitizers: `Address`, `HWAddress`, `Undefined`, `Memory`, `MemoryWithOrigins`, `Leak`, `Address,Undefined`.
+
+For example, to compile with both `Address` and `Undefined` sanitizers, use the following (recommended for development and testing, and tested in CI):
+
+```bash
+mkdir build-san
+cd build-san
+
+cmake -DCMAKE_BUILD_TYPE=Debug -DPEPARSE_ENABLE_TESTING=ON -DPEPARSE_USE_SANITIZER=Address,Undefined ..
+cmake --build .
+```
+
 ## Using the library
 
 Once the library is installed, linking to it is easy! Add the following lines in your CMake project:
diff --git a/cmake/compilation_flags.cmake b/cmake/compilation_flags.cmake
index b856209..395f1b5 100644
--- a/cmake/compilation_flags.cmake
+++ b/cmake/compilation_flags.cmake
@@ -2,6 +2,9 @@ set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
 
+include("cmake/sanitizers.cmake")
+process_sanitizer(PEPARSE)
+
 if (MSVC)
   list(APPEND DEFAULT_CXX_FLAGS /W4 /analyze)
 
diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake
new file mode 100644
index 0000000..a86e336
--- /dev/null
+++ b/cmake/sanitizers.cmake
@@ -0,0 +1,173 @@
+# Enable C/C++ sanitizers in your CMake project by `include`ing this file
+# somewhere in your CMakeLists.txt.
+#
+# Example:
+#
+#   include("cmake/sanitizers.cmake")
+#   process_sanitizer(MY_PROJECT)
+#
+# Example CMake configuration usage for `MY_PROJECT`:
+#
+#   cmake -Bbuild-asan-ubsan -H. -DMY_PROJECT_USE_SANITIZER=Address,Undefined
+#
+# where "MY_PROJECT" can by any arbitrary text that will be prepended to some
+# expected CMake variables--see below.
+#
+# This file expects the following variables to be set during CMake
+# configuration, where the prefix is set by the caller of `process_sanitizer`:
+#
+#   - *_USE_SANITIZER
+#     - A string value that is one of the following:
+#       - Address
+#       - HWAddress
+#       - Memory
+#       - MemoryWithOrigins
+#       - Undefined
+#       - Thread
+#       - DataFlow
+#       - Leak
+#       - Address,Undefined
+#
+#   - *_OPTIMIZE_SANITIZED_BUILDS
+#     - A boolean value to set whether a higher optimization is used in debug
+#       builds
+#
+#   - *_BLACKLIST_FILE
+#     - A filepath to a sanitizer blacklist file.
+
+
+function(append value)
+  foreach(variable ${ARGN})
+    set(${variable}
+        "${${variable}} ${value}"
+        PARENT_SCOPE)
+  endforeach(variable)
+endfunction()
+
+
+macro(append_common_sanitizer_flags prefix)
+  if (NOT MSVC)
+    # Append -fno-omit-frame-pointer and turn on debug info to get better
+    # stack traces.
+    append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+    append("-fno-optimize-sibling-calls" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+    append("-gline-tables-only" CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE
+                                CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZE_REL)
+    # Use -O1 even in debug mode, otherwise sanitizers slowdown is too large.
+    if (${prefix}_OPTIMIZE_SANITIZED_BUILDS)
+      message(STATUS "Optimizing sanitized Debug build")
+      append("-O1" CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
+    endif()
+  else()
+    # Keep frame pointers around.
+    append("/Oy-" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+    # Always ask the linker to produce symbols with asan.
+    append("/Zi" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+    # Workaround for incompatible (warning-producing) default CMake flag
+    # https://docs.microsoft.com/en-us/cpp/sanitizers/asan-known-issues
+    # https://gitlab.kitware.com/cmake/cmake/-/issues/19084
+    string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+    append("/debug" CMAKE_EXE_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS)
+  endif()
+endmacro()
+
+
+# Main logic
+macro(process_sanitizer prefix)
+
+  # Add options for the project to use sanitizers
+  option(${prefix}_USE_SANITIZER "Enable building with sanitizer support. Options are: Address, HWAddress, Memory, MemoryWithOrigins, Undefined, Thread, DataFlow, Leak, 'Address,Undefined'" false)
+  if (UNIX)
+    option(${prefix}_OPTIMIZE_SANITIZED_BUILDS "Optimize builds that use sanitization" false)
+    option(${prefix}_BLACKLIST_FILE "Path to blacklist file for sanitizers" "")
+    option(${prefix}_USE_SANITIZE_COVERAGE "Set for libFuzzer-required instrumentation, no linking." false)
+  endif()
+
+  if (${prefix}_USE_SANITIZER)
+    if(UNIX)
+
+      if(${prefix}_USE_SANITIZER STREQUAL "Address")
+        message(STATUS "Building with Address sanitizer")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      elseif(${prefix}_USE_SANITIZER STREQUAL "HWAddress")
+        message(STATUS "Building with Address sanitizer")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=hwaddress" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      elseif(${prefix}_USE_SANITIZER MATCHES "Memory(WithOrigins)?")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=memory" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+        if(${prefix}_USE_SANITIZER STREQUAL "MemoryWithOrigins")
+          message(STATUS "Building with MemoryWithOrigins sanitizer")
+          append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+        else()
+          message(STATUS "Building with Memory sanitizer")
+        endif()
+
+      elseif(${prefix}_USE_SANITIZER STREQUAL "Undefined")
+        message(STATUS "Building with Undefined sanitizer")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+        # Execution error on undefined detection. Could be optional to add this
+        append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      elseif(${prefix}_USE_SANITIZER STREQUAL "Thread")
+        message(STATUS "Building with Thread sanitizer")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      elseif(${prefix}_USE_SANITIZER STREQUAL "DataFlow")
+        message(STATUS "Building with DataFlow sanitizer")
+        append("-fsanitize=dataflow" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      elseif(${prefix}_USE_SANITIZER STREQUAL "Leak")
+        message(STATUS "Building with Leak sanitizer")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      elseif(${prefix}_USE_SANITIZER STREQUAL "Address,Undefined"
+          OR ${prefix}_USE_SANITIZER STREQUAL "Undefined,Address")
+        message(STATUS "Building with Address, Undefined sanitizers")
+        append_common_sanitizer_flags(${prefix})
+        append("-fsanitize=address,undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+        # Execution error on undefined detection. Could be optional to add this
+        append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+
+      else()
+        message(
+          FATAL_ERROR "Unsupported value of ${prefix}_USE_SANITIZER: '${${prefix}_USE_SANITIZER}'")
+      endif()
+    elseif(MSVC)
+      if(${prefix}_USE_SANITIZER STREQUAL "Address")
+        message(STATUS "Building with Address sanitizer")
+        append_common_sanitizer_flags(${prefix})
+        append("/fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+      else()
+        message(FATAL_ERROR "This sanitizer is not yet supported in the MSVC environment: '${${prefix}_USE_SANITIZER}'")
+      endif()
+    else()
+      message(FATAL_ERROR "${prefix}_USE_SANITIZER is not supported on this platform.")
+    endif()
+
+    # If specified, use a blacklist file
+    if (EXISTS "${${prefix}_BLACKLIST_FILE}")
+      message(STATUS "Using sanitizer blacklist file: ${${prefix}_BLACKLIST_FILE}")
+      append("-fsanitize-blacklist=${${prefix}_BLACKLIST_FILE}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+    endif()
+
+    # Set a once non-default option for more detection
+    if (${prefix}_USE_SANITIZER MATCHES "(Undefined,)?Address(,Undefined)?")
+      if (UNIX)
+        append("-fsanitize-address-use-after-scope" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+      endif()
+    endif()
+
+    # Set for libFuzzer-required instrumentation, no linking.
+    if (${prefix}_USE_SANITIZE_COVERAGE)
+      message(STATUS "Setting up sanitizer for coverage support with 'fuzzer-no-link'")
+      append("-fsanitize=fuzzer-no-link" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+    endif()
+  endif()
+endmacro()