diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # ChangeLog for ech-config
 
+## 0.0.2
+
+* Refuse trailing data and ignore ECHConfigs of unknown versions.
+  [#621](https://github.com/haskell-tls/hs-tls/pull/621)
+
 ## 0.0.1
 
 * Making Show more friendly to dnsext-*
diff --git a/Network/TLS/ECH/Config.hs b/Network/TLS/ECH/Config.hs
--- a/Network/TLS/ECH/Config.hs
+++ b/Network/TLS/ECH/Config.hs
@@ -33,10 +33,12 @@
 ) where
 
 import qualified Control.Exception as E
+import Control.Monad (unless, when)
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Base16 as B16
 import qualified Data.ByteString.Char8 as C8
 import Data.Char (isDigit)
+import Data.Maybe (catMaybes)
 import Data.Word
 import Network.ByteOrder
 import System.FilePath (takeFileName)
@@ -221,7 +223,7 @@
 ----------------------------------------------------------------
 
 -- | Type for configuration of encrypted client hello.
-data ECHConfig = ECHConfig
+newtype ECHConfig = ECHConfig
     { contents :: ECHConfigContents
     }
     deriving (Eq, Ord)
@@ -233,18 +235,26 @@
     show ECHConfig{..} = show contents
 
 getECHConfig :: ReadBuffer -> IO ECHConfig
-getECHConfig rbuf = do
-    _ver <- read16 rbuf
-    _len <- read16 rbuf
-    ECHConfig <$> getECHConfigContents rbuf
+getECHConfig rbuf =
+    getECHConfig' rbuf >>= maybe (fail "unknown ECHConfig version") return
 
+-- The contents fill the length exactly.  One of an unknown version is
+-- skipped by its length and gives Nothing.
+getECHConfig' :: ReadBuffer -> IO (Maybe ECHConfig)
+getECHConfig' rbuf = do
+    ver <- read16 rbuf
+    body <- getOpaque16 rbuf
+    if ver /= 0xfe0d
+        then return Nothing
+        else Just . ECHConfig <$> withReadBuffer body (whole getECHConfigContents)
+
 putECHConfig :: WriteBuffer -> ECHConfig -> IO ()
 putECHConfig wbuf ECHConfig{..} = do
     write16 wbuf 0xfe0d
     withLength16 wbuf $ putECHConfigContents wbuf contents
 
 sizeOfECHConfig :: ECHConfig -> Int
-sizeOfECHConfig cnf = sizeof cnf
+sizeOfECHConfig = sizeof
 
 -- | Encoder for 'ECHConfig'.
 encodeECHConfig :: ECHConfig -> ByteString
@@ -258,7 +268,7 @@
     unsafePerformIO $
         E.handle handler $
             withReadBuffer bs $
-                \rbuf -> Just <$> getECHConfig rbuf
+                \rbuf -> Just <$> whole getECHConfig rbuf
   where
     handler (E.SomeException _) = return Nothing
 
@@ -267,8 +277,9 @@
 -- | A list of 'ECHConfig'.
 type ECHConfigList = [ECHConfig]
 
+-- | ECHConfigs of unknown versions are ignored (RFC 9849 Section 4).
 getECHConfigList :: ReadBuffer -> IO [ECHConfig]
-getECHConfigList rbuf = getList16 rbuf getECHConfig
+getECHConfigList rbuf = catMaybes <$> getList16 rbuf getECHConfig'
 
 putECHConfigList :: WriteBuffer -> [ECHConfig] -> IO ()
 putECHConfigList wbuf configs =
@@ -290,7 +301,7 @@
     unsafePerformIO $
         E.handle handler $
             withReadBuffer bs $
-                \rbuf -> Just <$> getECHConfigList rbuf
+                \rbuf -> Just <$> whole getECHConfigList rbuf
   where
     handler (E.SomeException _) = return Nothing
 
@@ -299,7 +310,7 @@
 loadECHConfigList :: FilePath -> IO [ECHConfig]
 loadECHConfigList file = do
     bs <- C8.readFile file
-    withReadBuffer bs $ getECHConfigList
+    withReadBuffer bs getECHConfigList
 
 -- | Loading secret keys stored in files whose names
 -- are "\<num\>.key".
@@ -349,7 +360,18 @@
             then do
                 x <- parer rbuf
                 loop lim (build . (x :))
-            else return $ build []
+            else do
+                -- The last element must not run past the list.
+                when (cur > lim) $ fail "list overrun"
+                return $ build []
+
+-- Fails unless the parser consumes the whole buffer.
+whole :: (ReadBuffer -> IO a) -> ReadBuffer -> IO a
+whole parser rbuf = do
+    x <- parser rbuf
+    r <- remainingSize rbuf
+    unless (r == 0) $ fail "trailing data"
+    return x
 
 withLength16 :: WriteBuffer -> IO () -> IO ()
 withLength16 wbuf builder = do
diff --git a/ech-config.cabal b/ech-config.cabal
--- a/ech-config.cabal
+++ b/ech-config.cabal
@@ -1,6 +1,6 @@
-cabal-version:      >=1.10
+cabal-version:      2.0
 name:               ech-config
-version:            0.0.1
+version:            0.0.2
 license:            BSD3
 license-file:       LICENSE
 maintainer:         kazu@iij.ad.jp
@@ -27,7 +27,7 @@
         base16-bytestring,
         bytestring,
         filepath,
-        network-byte-order
+        network-byte-order >=0.1.7
 
 executable ech-gen
     main-is:            ech-gen.hs
@@ -50,4 +50,4 @@
 source-repository head
     type:     git
     location: https://github.com/haskell-tls/hs-tls
-    subdir:   tls
+    subdir:   ech-config
diff --git a/util/ech-gen.hs b/util/ech-gen.hs
--- a/util/ech-gen.hs
+++ b/util/ech-gen.hs
@@ -99,7 +99,7 @@
             BS.writeFile secfileP secPEM
             {-
                         let secfileN = num ++ ".nss"
-                            x = magic <> skm -- FIXME: pk is neccessary, sigh.
+                            x = magic <> skm -- FIXME: pk is necessary, sigh.
                             len = BS.length x
                             (a, b) = len `divMod` 256
                             y = BS.pack [fromIntegral a, fromIntegral b] <> x <> encodedConfigList
