diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+## 0.5.0.0
+
+* Add `AffectedApi` type and `affectedApi` field in `Affected`
+
+## 0.4.0.0
+
+* Introduce `OsvId` and use it in `Advisory.{aliases,related}`
+
+## 0.3.0.2
+
+* Update `cvss` dependency bounds
+
+## 0.3.0.0
+
+* Add `Repository` and `ComponentIdentifier` in `Security.Advisories.Core.Advisory`
+
+## 0.2.1.0
+
+* Introduce `isVersionAffectedBy` and `isVersionRangeAffectedBy` in `Security.Advisories.Core`
+
 ## 0.2.0.2
 
 * Update `osv` dependency bounds
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -11,5 +11,5 @@
 Run (and auto update) the golden test:
 
 ```ShellSession
-cabal test -O0 --test-show-details=direct --test-option=--accept
+cabal test -O0 --test-show-details=direct
 ```
diff --git a/hsec-core.cabal b/hsec-core.cabal
--- a/hsec-core.cabal
+++ b/hsec-core.cabal
@@ -1,43 +1,56 @@
-cabal-version:      2.4
-name:               hsec-core
-version:            0.2.0.2
+cabal-version:   2.4
+name:            hsec-core
+version:         0.5.0.0
 
 -- A short (one-line) description of the package.
-synopsis:           Core package representing Haskell advisories
+synopsis:        Core package representing Haskell advisories
 
 -- A longer description of the package.
-description:        Core package representing Haskell advisories.
+description:     Core package representing Haskell advisories.
 
 -- A URL where users can report bugs.
 -- bug-reports:
 
 -- The license under which the package is released.
-license:            BSD-3-Clause
-author:             Haskell Security Response Team
-maintainer:         security-advisories@haskell.org
+license:         BSD-3-Clause
+author:          Haskell Security Response Team
+maintainer:      security-advisories@haskell.org
 
 -- A copyright notice.
 -- copyright:
-category:           Data
-extra-doc-files:    CHANGELOG.md, README.md
+category:        Data
+extra-doc-files:
+  CHANGELOG.md
+  README.md
 
 tested-with:
-  GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.6 || ==9.8.3 || ==9.10.1 || ==9.12.1
+  GHC ==8.10.7
+   || ==9.0.2
+   || ==9.2.8
+   || ==9.4.8
+   || ==9.6.6
+   || ==9.8.3
+   || ==9.10.1
+   || ==9.12.1
 
 library
   exposed-modules:
     Security.Advisories.Core.Advisory
     Security.Advisories.Core.HsecId
+    Security.Advisories.Core.OsvId
 
   build-depends:
     , base          >=4.14    && <5
     , Cabal-syntax  >=3.8.1.0 && <3.15
-    , cvss >= 0.2 && < 0.3
-    , osv >= 0.1 && < 0.3
+    , containers    >=0.6     && <0.8
+    , cvss          >=0.2     && <0.4
+    , network-uri   >=2.6.3.0 && <2.8
+    , osv           >=0.1     && <0.3
     , pandoc-types  >=1.22    && <2
     , safe          >=0.3     && <0.4
     , text          >=1.2     && <3
     , time          >=1.9     && <1.15
+
   hs-source-dirs:   src
   default-language: Haskell2010
   ghc-options:
@@ -48,12 +61,14 @@
   type:             exitcode-stdio-1.0
   hs-source-dirs:   test
   main-is:          Spec.hs
+  other-modules:    Spec.QueriesSpec
   build-depends:
     , base
+    , Cabal-syntax
     , cvss
     , hsec-core
-    , tasty        <2
-    , tasty-hunit  <0.11
+    , tasty         <2
+    , tasty-hunit   <0.11
     , text
 
   default-language: Haskell2010
diff --git a/src/Security/Advisories/Core/Advisory.hs b/src/Security/Advisories/Core/Advisory.hs
--- a/src/Security/Advisories/Core/Advisory.hs
+++ b/src/Security/Advisories/Core/Advisory.hs
@@ -1,59 +1,93 @@
-{-# LANGUAGE DerivingVia, OverloadedStrings #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
 
 module Security.Advisories.Core.Advisory
-  ( Advisory(..)
+  ( Advisory (..),
+
     -- * Supporting types
-  , Affected(..)
-  , CAPEC(..)
-  , CWE(..)
-  , Architecture(..)
-  , AffectedVersionRange(..)
-  , OS(..)
-  , Keyword(..)
-  , ComponentIdentifier(..)
-  , GHCComponent(..)
-  , ghcComponentToText
-  , ghcComponentFromText
+    Affected (..),
+    CAPEC (..),
+    CWE (..),
+    Architecture (..),
+    AffectedVersionRange (..),
+    OS (..),
+    Keyword (..),
+    AffectedApi (..),
+    ComponentIdentifier (..),
+    GHCComponent (..),
+    RepositoryURL (..),
+    RepositoryName (..),
+    PackageName,
+    mkPackageName,
+    unPackageName,
+    ghcComponentToText,
+    ghcComponentFromText,
+    hackage,
+
+    -- * Queries
+    isVersionAffectedBy,
+    isVersionRangeAffectedBy,
   )
-  where
+where
 
 import Data.Text (Text)
 import Data.Time (UTCTime)
+import Distribution.Types.PackageName (PackageName, mkPackageName, unPackageName)
 import Distribution.Types.Version (Version)
-import Distribution.Types.VersionRange (VersionRange)
-
-import Text.Pandoc.Definition (Pandoc)
-
+import Distribution.Types.VersionInterval (asVersionIntervals)
+import Distribution.Types.VersionRange (VersionRange, anyVersion, earlierVersion, intersectVersionRanges, noVersion, orLaterVersion, unionVersionRanges, withinRange)
+import Network.URI (URI)
+import Network.URI.Static (uri)
 import Security.Advisories.Core.HsecId (HsecId)
+import Security.Advisories.Core.OsvId (OsvId)
 import qualified Security.CVSS as CVSS
 import Security.OSV (Reference)
+import Text.Pandoc.Definition (Pandoc)
 
 data Advisory = Advisory
-  { advisoryId :: HsecId
-  , advisoryModified :: UTCTime
-  , advisoryPublished :: UTCTime
-  , advisoryCAPECs :: [CAPEC]
-  , advisoryCWEs :: [CWE]
-  , advisoryKeywords :: [Keyword]
-  , advisoryAliases :: [Text]
-  , advisoryRelated :: [Text]
-  , advisoryAffected :: [Affected]
-  , advisoryReferences :: [Reference]
-  , advisoryPandoc :: Pandoc  -- ^ Parsed document, without TOML front matter
-  , advisoryHtml :: Text
-  , advisorySummary :: Text
-    -- ^ A one-line, English textual summary of the vulnerability
-  , advisoryDetails :: Text
-    -- ^ Details of the vulnerability (CommonMark), without TOML front matter
+  { advisoryId :: HsecId,
+    advisoryModified :: UTCTime,
+    advisoryPublished :: UTCTime,
+    advisoryCAPECs :: [CAPEC],
+    advisoryCWEs :: [CWE],
+    advisoryKeywords :: [Keyword],
+    advisoryAliases :: [OsvId],
+    advisoryRelated :: [OsvId],
+    advisoryAffected :: [Affected],
+    advisoryReferences :: [Reference],
+    -- | Parsed document, without TOML front matter
+    advisoryPandoc :: Pandoc,
+    advisoryHtml :: Text,
+    -- | A one-line, English textual summary of the vulnerability
+    advisorySummary :: Text,
+    -- | Details of the vulnerability (CommonMark), without TOML front matter
+    advisoryDetails :: Text
   }
   deriving stock (Show)
 
-data ComponentIdentifier = Hackage Text | GHC GHCComponent
+data ComponentIdentifier
+  = Repository RepositoryURL RepositoryName PackageName
+  | GHC GHCComponent
   deriving stock (Show, Eq)
 
+hackage :: PackageName -> ComponentIdentifier
+hackage =
+  Repository
+    (RepositoryURL [uri|https://hackage.haskell.org|])
+    (RepositoryName "hackage")
+
+newtype RepositoryURL
+  = RepositoryURL {unRepositoryURL :: URI}
+  deriving stock (Eq, Ord, Show)
+
+newtype RepositoryName
+  = RepositoryName {unRepositoryName :: Text}
+  deriving stock (Eq, Ord, Show)
+
 -- Keep this list in sync with the 'ghcComponentFromText' below
 data GHCComponent = GHCCompiler | GHCi | GHCRTS | GHCPkg | RunGHC | IServ | HP2PS | HPC | HSC2HS | Haddock
-  deriving stock (Show, Eq, Enum, Bounded)
+  deriving stock (Show, Eq, Ord, Enum, Bounded)
 
 ghcComponentToText :: GHCComponent -> Text
 ghcComponentToText c = case c of
@@ -85,12 +119,13 @@
 -- | An affected package (or package component).  An 'Advisory' must
 -- mention one or more packages.
 data Affected = Affected
-  { affectedComponentIdentifier :: ComponentIdentifier
-  , affectedCVSS :: CVSS.CVSS
-  , affectedVersions :: [AffectedVersionRange]
-  , affectedArchitectures :: Maybe [Architecture]
-  , affectedOS :: Maybe [OS]
-  , affectedDeclarations :: [(Text, VersionRange)]
+  { affectedComponentIdentifier :: ComponentIdentifier,
+    affectedCVSS :: CVSS.CVSS,
+    affectedVersions :: [AffectedVersionRange],
+    affectedArchitectures :: Maybe [Architecture],
+    affectedOS :: Maybe [OS],
+    affectedDeclarations :: [(Text, VersionRange)],
+    affectedApi :: [AffectedApi]
   }
   deriving stock (Eq, Show)
 
@@ -142,8 +177,53 @@
   deriving stock (Eq, Ord)
   deriving (Show) via Text
 
+data AffectedApi = AffectedApi
+  { affectedApiModule :: Text,
+    affectedApiName :: Text
+  }
+  deriving stock (Eq, Show)
+
 data AffectedVersionRange = AffectedVersionRange
   { affectedVersionRangeIntroduced :: Version,
     affectedVersionRangeFixed :: Maybe Version
   }
   deriving stock (Eq, Show)
+
+-- * Queries
+
+-- | Check whether a component and a version is concerned by an advisory
+--
+-- Since @0.2.1.0@
+isVersionAffectedBy :: ComponentIdentifier -> Version -> Advisory -> Bool
+isVersionAffectedBy = isAffectedByHelper withinRange
+
+-- | Check whether a component and a version range is concerned by an advisory
+--
+-- Since @0.2.1.0@
+isVersionRangeAffectedBy :: ComponentIdentifier -> VersionRange -> Advisory -> Bool
+isVersionRangeAffectedBy = isAffectedByHelper $
+  \queryVersionRange affectedVersionRange ->
+    isSomeVersion (affectedVersionRange `intersectVersionRanges` queryVersionRange)
+  where
+    isSomeVersion :: VersionRange -> Bool
+    isSomeVersion range
+      | [] <- asVersionIntervals range = False
+      | otherwise = True
+
+-- | Helper function for 'isVersionAffectedBy' and 'isVersionRangeAffectedBy'
+isAffectedByHelper :: (a -> VersionRange -> Bool) -> ComponentIdentifier -> a -> Advisory -> Bool
+isAffectedByHelper checkWithRange queryComponent queryVersionish =
+  any checkAffected . advisoryAffected
+  where
+    checkAffected :: Affected -> Bool
+    checkAffected affected =
+      affectedComponentIdentifier affected == queryComponent && checkWithRange queryVersionish (fromAffected affected)
+
+    fromAffected :: Affected -> VersionRange
+    fromAffected = foldr (unionVersionRanges . fromAffectedVersionRange) noVersion . affectedVersions
+
+    fromAffectedVersionRange :: AffectedVersionRange -> VersionRange
+    fromAffectedVersionRange avr =
+      intersectVersionRanges
+        (orLaterVersion (affectedVersionRangeIntroduced avr))
+        (maybe anyVersion earlierVersion (affectedVersionRangeFixed avr))
diff --git a/src/Security/Advisories/Core/OsvId.hs b/src/Security/Advisories/Core/OsvId.hs
new file mode 100644
--- /dev/null
+++ b/src/Security/Advisories/Core/OsvId.hs
@@ -0,0 +1,106 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Security.Advisories.Core.OsvId
+  ( OsvId,
+    parseOsvId,
+    printOsvId,
+    osvIdPrefix,
+  )
+where
+
+import qualified Data.List as L
+import Data.Maybe (listToMaybe)
+import qualified Data.Set as Set
+import Data.Text (Text)
+import qualified Data.Text as T
+
+newtype OsvId = OsvId Text
+  deriving (Eq, Ord, Show)
+
+knownPrefixes :: Set.Set Text
+knownPrefixes =
+  Set.fromList
+    [ "ASB-A",
+      "PUB-A",
+      "ALSA",
+      "ALBA",
+      "ALEA",
+      "ALPINE",
+      "BELL",
+      "BIT",
+      "CGA",
+      "CleanStart",
+      "CURL",
+      "CVE",
+      "DEBIAN",
+      "DHI",
+      "DRUPAL",
+      "DSA",
+      "DLA",
+      "DTSA",
+      "ECHO",
+      "EEF",
+      "ELA",
+      "GHSA",
+      "GO",
+      "GSD",
+      "HSEC",
+      "JLSEC",
+      "KUBE",
+      "LBSEC",
+      "LSN",
+      "MGASA",
+      "MAL",
+      "MINI",
+      "OESA",
+      "OSEC",
+      "OSV",
+      "PHSA",
+      "PSF",
+      "PYSEC",
+      "RHSA",
+      "RHBA",
+      "RHEA",
+      "RLSA",
+      "RXSA",
+      "RSEC",
+      "ROOT",
+      "RUSTSEC",
+      "SUSE-SU",
+      "SUSE-RU",
+      "SUSE-FU",
+      "SUSE-OU",
+      "openSUSE-SU",
+      "UBUNTU",
+      "USN",
+      "V8"
+    ]
+
+sortedPrefixes :: [Text]
+sortedPrefixes = L.reverse . L.sortOn T.length $ Set.toList knownPrefixes
+
+parseOsvId :: Text -> Maybe OsvId
+parseOsvId t = do
+  prefix <- findPrefix t
+  let rest = T.drop (T.length prefix + 1) t
+  guard (not (T.null rest))
+  pure (OsvId t)
+  where
+    findPrefix txt =
+      listToMaybe $
+        filter (\p -> T.isPrefixOf (p <> "-") txt) sortedPrefixes
+
+    guard True = Just ()
+    guard False = Nothing
+
+printOsvId :: OsvId -> Text
+printOsvId (OsvId t) = t
+
+osvIdPrefix :: OsvId -> Text
+osvIdPrefix (OsvId t) = case findPrefix t of
+  Just p -> p
+  Nothing -> T.takeWhile (/= '-') t
+  where
+    findPrefix txt =
+      listToMaybe $
+        filter (\p -> T.isPrefixOf (p <> "-") txt) sortedPrefixes
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,10 +1,11 @@
 module Main where
 
 import Test.Tasty
+import qualified Spec.QueriesSpec as QueriesSpec
 
 main :: IO ()
 main =
   defaultMain $
     testGroup
       "Tests"
-      []
+      [QueriesSpec.spec]
diff --git a/test/Spec/QueriesSpec.hs b/test/Spec/QueriesSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec/QueriesSpec.hs
@@ -0,0 +1,195 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Spec.QueriesSpec (spec) where
+
+import Data.Bifunctor (first)
+import Data.Either (fromRight)
+import Data.Maybe (fromMaybe)
+import qualified Data.Maybe as Maybe
+import Data.Text (Text)
+import qualified Data.Text as T
+import Distribution.Parsec (eitherParsec)
+import Distribution.Types.Version (alterVersion, version0)
+import Distribution.Types.VersionRange (VersionRange, VersionRangeF (..), anyVersion, projectVersionRange)
+import Security.Advisories.Core.Advisory
+import Security.Advisories.Core.HsecId
+import Security.Advisories.Core.OsvId (parseOsvId)
+import Security.CVSS (parseCVSS)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+spec :: TestTree
+spec =
+  testGroup
+    "Queries"
+    [ testGroup "isAffectedBy" $
+        flip concatMap cases $
+          map $ \(actual, query, expected) ->
+            let title x y =
+                  if expected
+                    then show x <> " is vulnerable to " <> show y
+                    else show x <> " is not vulnerable to " <> show y
+                versionRange x =
+                  either (\e -> error $ "Cannot parse version range " <> show x <> " : " <> show e) id $
+                    parseVersionRange $
+                      if x == ""
+                        then Nothing
+                        else Just x
+             in testCase (title actual query) $
+                  let query' = versionRange query
+                      affectedVersion' = versionRange actual
+                   in isVersionRangeAffectedBy component query' (mkAdvisory affectedVersion')
+                        @?= expected
+    ]
+
+cases :: [[(Text, Text, Bool)]]
+cases =
+  [ reversible ("", "", True),
+    reversible ("", "==1", True),
+    reversible ("==1.1", "<=2", True),
+    reversible ("==1.1", ">1", True),
+    reversible ("==1", "==1", True),
+    reversible ("==2||==1", "==1", True),
+    reversible ("==1.1", "<=2&&>1", True),
+    reversible ("^>=1", ">1&&<1.2", True),
+    reversible (">=1", "==2", True),
+    reversible (">=1", "==1", True),
+    reversible ("==2", ">=2", True),
+    reversible ("==2", ">1", True),
+    reversible (">5", ">=2", True),
+    reversible (">5", ">2", True),
+    reversible ("==5", ">=2", True),
+    reversible ("==5", ">2", True),
+    reversible (">=5", ">2", True),
+    reversible ("<=5", ">2", True),
+    reversible ("<=5", "<=2", True),
+    reversible ("<5", ">=2", True),
+    reversible (">=2", "==5", True),
+    reversible (">2", "==5", True),
+    reversible (">5", ">=5", True),
+    reversible ("^>=1.1", ">1", True),
+    reversible ("^>=1.1", "<2", True),
+    reversible ("^>=1.1", "<=1.2", True),
+    reversible ("^>=1.1", ">1.1", True),
+    reversible ("^>=1.1", ">=1.1.5", True),
+    reversible ("^>=1.1", ">=1", True),
+    reversible ("==1.1", "<1", False),
+    reversible ("==2.1", "<=2", False),
+    reversible ("==1", ">1.1", False),
+    reversible ("==2", "==1", False),
+    reversible ("==2||==1", ">3", False),
+    notReversible ("<=2&&>1", "==3", True),
+    reversible (">=2", "==1.1", False),
+    reversible (">=1.1", "==1", False),
+    reversible ("==2", ">=2.1", False),
+    notReversible (">1", "==1", False),
+    reversible ("<2", ">=2", False),
+    reversible ("==2", ">=5", False),
+    reversible ("==2", ">5", False),
+    reversible ("<=2", ">5", False),
+    reversible ("<=2", ">5", False),
+    reversible ("<2", ">=5", False),
+    reversible (">=2", "==1.1", False),
+    reversible ("<2", "==5", False),
+    reversible ("<5", ">=5", False),
+    reversible ("^>=1.1", "<1", False),
+    reversible ("^>=1.1", "<1.1", False),
+    reversible ("^>=1.1", ">=1.2", False),
+    reversible ("^>=1.1", "<=1", False),
+    reversible ("^>=1.1", ">2", False),
+    reversible ("^>=1", ">=2", False)
+  ]
+  where
+    reversible (query, affectedVersion, expected) = [(query, affectedVersion, expected), (query, affectedVersion, expected)]
+    notReversible (query, affectedVersion, expected) = [(query, affectedVersion, expected), (affectedVersion, query, not expected)]
+
+mkAdvisory :: VersionRange -> Advisory
+mkAdvisory versionRange =
+  Advisory
+    { advisoryId = fromMaybe (error "Cannot mkHsecId") $ mkHsecId 2023 42,
+      advisoryModified = read "2023-01-01T00:00:00",
+      advisoryPublished = read "2023-01-01T00:00:00",
+      advisoryCAPECs = [],
+      advisoryCWEs = [],
+      advisoryKeywords = [],
+      advisoryAliases = Maybe.fromJust . traverse parseOsvId $ ["CVE-2022-1234"],
+      advisoryRelated = Maybe.fromJust . traverse parseOsvId $ ["CVE-2022-5678", "CVE-2022-9012"],
+      advisoryAffected =
+        [ Affected
+            { affectedComponentIdentifier = component,
+              affectedCVSS = cvss,
+              affectedVersions = mkAffectedVersions versionRange,
+              affectedArchitectures = Nothing,
+              affectedOS = Nothing,
+              affectedDeclarations = [],
+              affectedApi = []
+            }
+        ],
+      advisoryReferences = [],
+      advisoryPandoc = mempty,
+      advisoryHtml = "",
+      advisorySummary = "",
+      advisoryDetails = ""
+    }
+  where
+    cvss = fromRight (error "Cannot parseCVSS") (parseCVSS "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
+
+mkAffectedVersions :: VersionRange -> [AffectedVersionRange]
+mkAffectedVersions vr =
+  let fixed from to =
+        AffectedVersionRange
+          { affectedVersionRangeIntroduced = from,
+            affectedVersionRangeFixed = Just to
+          }
+      onlyFixed to =
+        AffectedVersionRange
+          { affectedVersionRangeIntroduced = version0,
+            affectedVersionRangeFixed = Just to
+          }
+      vulnerable from =
+        AffectedVersionRange
+          { affectedVersionRangeIntroduced = from,
+            affectedVersionRangeFixed = Nothing
+          }
+      nextMinor =
+        \case
+          [] -> [1]
+          [x] -> [x, 1]
+          [x, y] -> [x, y, 1]
+          [x, y, z] -> [x, y, z, 1]
+          [w, x, y, z] -> [w, x, y, z + 1]
+          xs -> xs ++ [1]
+      previousMinor =
+        \case
+          [] -> [0]
+          [x] -> [x - 1, 99]
+          [x, y] -> [x, y - 1, 99]
+          [x, y, z] -> [x, y, z - 1, 99]
+          [w, x, y, z] -> [w, x, y, z - 1]
+          _ -> error "TODO"
+      mkMajorBoundVersion =
+        \case
+          [] -> [0]
+          [x] -> [x, 1]
+          (x : y : _) -> [x, y + 1]
+   in case projectVersionRange vr of
+        ThisVersionF x -> [fixed x $ alterVersion (<> [0, 0, 1]) x]
+        LaterVersionF x -> [vulnerable $ alterVersion nextMinor x]
+        OrLaterVersionF x -> [vulnerable x]
+        EarlierVersionF x -> [onlyFixed $ alterVersion previousMinor x]
+        OrEarlierVersionF x -> [onlyFixed x]
+        MajorBoundVersionF x -> [fixed x $ alterVersion mkMajorBoundVersion x]
+        UnionVersionRangesF x y -> mkAffectedVersions x <> mkAffectedVersions y
+        IntersectVersionRangesF x y ->
+          [ low {affectedVersionRangeFixed = affectedVersionRangeFixed high}
+            | low <- mkAffectedVersions x,
+              high <- mkAffectedVersions y
+          ]
+
+component :: ComponentIdentifier
+component = hackage $ mkPackageName "package-name"
+
+-- | Parse 'VersionRange' as given to the CLI
+parseVersionRange :: Maybe Text -> Either Text VersionRange
+parseVersionRange = maybe (return anyVersion) (first T.pack . eitherParsec . T.unpack)
