diff --git a/EXAMPLE_ADVISORY.md b/EXAMPLE_ADVISORY.md index f56c2650..bdc35867 100644 --- a/EXAMPLE_ADVISORY.md +++ b/EXAMPLE_ADVISORY.md @@ -30,7 +30,12 @@ related = ["CVE-2022-YYYY", "CVE-2022-ZZZZ"] # OPTION 1: package = hackage-package-name package = "package-name" # -# OPTION 2: ghc-component = {ghc,ghci,rts,ghc-pkg,runghc,ghc-iserv,hp2ps,hpc,hsc2hs,haddock} +# OPTION 2: alternative-hackage-package = hackage-package-name +# repository-url = "https//hackage.example.org/" +# repository-name = "example" +# package = "package-name" +# +# OPTION 3: ghc-component = {ghc,ghci,rts,ghc-pkg,runghc,ghc-iserv,hp2ps,hpc,hsc2hs,haddock} # ghc-component = "ghci" # CVSS vector. Accepted versions: 2.0, 3.0, 3.1 diff --git a/code/hsec-core/CHANGELOG.md b/code/hsec-core/CHANGELOG.md index d4022fe1..c5eaf8e5 100644 --- a/code/hsec-core/CHANGELOG.md +++ b/code/hsec-core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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` diff --git a/code/hsec-core/hsec-core.cabal b/code/hsec-core/hsec-core.cabal index 83866c67..debdc79a 100644 --- a/code/hsec-core/hsec-core.cabal +++ b/code/hsec-core/hsec-core.cabal @@ -1,6 +1,6 @@ cabal-version: 2.4 name: hsec-core -version: 0.2.1.0 +version: 0.3.0.0 -- A short (one-line) description of the package. synopsis: Core package representing Haskell advisories diff --git a/code/hsec-core/src/Security/Advisories/Core/Advisory.hs b/code/hsec-core/src/Security/Advisories/Core/Advisory.hs index 28a12772..cc4f6d9e 100644 --- a/code/hsec-core/src/Security/Advisories/Core/Advisory.hs +++ b/code/hsec-core/src/Security/Advisories/Core/Advisory.hs @@ -12,8 +12,14 @@ module Security.Advisories.Core.Advisory , Keyword(..) , ComponentIdentifier(..) , GHCComponent(..) + , RepositoryURL(..) + , RepositoryName(..) + , PackageName + , mkPackageName + , unPackageName , ghcComponentToText , ghcComponentFromText + , hackage -- * Queries , isVersionAffectedBy , isVersionRangeAffectedBy @@ -22,6 +28,7 @@ module Security.Advisories.Core.Advisory import Data.Text (Text) import Data.Time (UTCTime) +import Distribution.Types.PackageName (PackageName, mkPackageName, unPackageName) import Distribution.Types.Version (Version) import Distribution.Types.VersionInterval (asVersionIntervals) import Distribution.Types.VersionRange (VersionRange, anyVersion, earlierVersion, intersectVersionRanges, noVersion, orLaterVersion, unionVersionRanges, withinRange) @@ -52,9 +59,22 @@ data Advisory = Advisory } 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 "https://hackage.haskell.org") (RepositoryName "hackage") + +newtype RepositoryURL + = RepositoryURL { unRepositoryURL :: Text } + 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) diff --git a/code/hsec-core/test/Spec/QueriesSpec.hs b/code/hsec-core/test/Spec/QueriesSpec.hs index d977b728..d15f5439 100644 --- a/code/hsec-core/test/Spec/QueriesSpec.hs +++ b/code/hsec-core/test/Spec/QueriesSpec.hs @@ -186,7 +186,7 @@ mkAffectedVersions vr = ] component :: ComponentIdentifier -component = Hackage "package-name" +component = hackage $ mkPackageName "package-name" -- | Parse 'VersionRange' as given to the CLI parseVersionRange :: Maybe Text -> Either Text VersionRange diff --git a/code/hsec-tools/CHANGELOG.md b/code/hsec-tools/CHANGELOG.md index d65574a9..2839d44c 100644 --- a/code/hsec-tools/CHANGELOG.md +++ b/code/hsec-tools/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0.1 + +* Bump `hsec-core` `0.3.0.0` + ## 0.3.0.0 * Move `isVersionAffectedBy` and `isVersionRangeAffectedBy` to `Security.Advisories.Core` (`hsec-core`) diff --git a/code/hsec-tools/app/Main.hs b/code/hsec-tools/app/Main.hs index e291561e..fde5cfeb 100644 --- a/code/hsec-tools/app/Main.hs +++ b/code/hsec-tools/app/Main.hs @@ -135,14 +135,18 @@ commandQuery = isAffected :: Parser (IO ()) isAffected = go - <$> argument (parseComponent <$> str) (metavar "PACKAGE|GHC:COMPONENT") + <$> argument (parseComponent <$> str) (metavar "PACKAGE|REPO:PACKAGE|GHC:COMPONENT") <*> optional (option versionRangeReader (metavar "VERSION-RANGE" <> short 'v' <> long "version-range")) <*> optional (option str (metavar "ADVISORIES-PATH" <> short 'p' <> long "advisories-path")) where parseComponent raw = - fromMaybe (Hackage raw) $ do - ghcComponentRaw <- T.stripPrefix "ghc:" $ T.toLower raw - GHC <$> ghcComponentFromText ghcComponentRaw + case T.breakOn ":" raw of + (pkg, "") -> hackage $ mkPackageName $ T.unpack pkg + (p, pkg) -> + let pkgName = mkPackageName $ T.unpack pkg + in if T.toCaseFold p == T.toCaseFold "ghc" + then fromMaybe (hackage pkgName) $ GHC <$> ghcComponentFromText p + else Repository (RepositoryURL "") (RepositoryName p) pkgName go :: ComponentIdentifier -> Maybe VersionRange -> Maybe FilePath -> IO () go component versionRange advisoriesPath = do let versionRange' = fromMaybe anyVersion versionRange diff --git a/code/hsec-tools/hsec-tools.cabal b/code/hsec-tools/hsec-tools.cabal index d87733b5..084723ab 100644 --- a/code/hsec-tools/hsec-tools.cabal +++ b/code/hsec-tools/hsec-tools.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: hsec-tools -version: 0.3.0.0 +version: 0.3.0.1 -- A short (one-line) description of the package. synopsis: @@ -65,7 +65,7 @@ library , directory <2 , extra >=1.7 && <1.9 , filepath >=1.4 && <1.6 - , hsec-core >= 0.2.1.0 && <0.3 + , hsec-core >= 0.3.0.0 && <0.4 , file-embed >=0.0.13.0 && <0.0.17 , lucid >=2.9.0 && < 3 , mtl >=2.2 && <2.4 @@ -109,7 +109,7 @@ executable hsec-tools , bytestring >=0.10 && <0.13 , Cabal-syntax >=3.8.1.0 && <3.15 , filepath >=1.4 && <1.6 - , hsec-core >= 0.2.1.0 && <0.3 + , hsec-core >= 0.3.0.0 && <0.4 , hsec-tools , optparse-applicative >=0.17 && <0.19 , text >=1.2 && <3 diff --git a/code/hsec-tools/src/Security/Advisories/Convert/OSV.hs b/code/hsec-tools/src/Security/Advisories/Convert/OSV.hs index c289f096..483b2245 100644 --- a/code/hsec-tools/src/Security/Advisories/Convert/OSV.hs +++ b/code/hsec-tools/src/Security/Advisories/Convert/OSV.hs @@ -52,7 +52,9 @@ mkPackage ecosystem = } where (ecosystemName, packageName) = case ecosystem of - Hackage n -> ("Hackage", n) + Repository _ repoName pkg + | ecosystem == hackage pkg -> ("Hackage", T.pack $ unPackageName pkg) + | otherwise -> (unRepositoryName repoName, T.pack $ unPackageName pkg) GHC c -> ("GHC", ghcComponentToText c) mkRange :: [AffectedVersionRange] -> OSV.Range Void diff --git a/code/hsec-tools/src/Security/Advisories/Format.hs b/code/hsec-tools/src/Security/Advisories/Format.hs index 19037c53..6a871ab7 100644 --- a/code/hsec-tools/src/Security/Advisories/Format.hs +++ b/code/hsec-tools/src/Security/Advisories/Format.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -Wno-orphans #-} @@ -15,7 +16,7 @@ module Security.Advisories.Format ) where -import Control.Applicative ((<|>)) +import Control.Applicative (asum) import Commonmark.Types (HasAttributes (..), IsBlock (..), IsInline (..), Rangeable (..), SourceRange (..)) import qualified Data.Map as Map import Data.Maybe (fromMaybe) @@ -157,9 +158,15 @@ instance Toml.ToValue GHCComponent where toValue = Toml.Text' () . ghcComponentToText instance Toml.FromValue Affected where - fromValue = Toml.parseTableFromValue $ - do ecosystem <- (Hackage <$> Toml.reqKey "package") <|> (GHC <$> Toml.reqKey "ghc-component") - cvss <- Toml.reqKey "cvss" -- TODO validate CVSS format + fromValue = + Toml.parseTableFromValue $ do + ecosystem <- + asum [ + Repository <$> Toml.reqKey "repository-url" <*> Toml.reqKey "repository-name" <*> Toml.reqKey "package", + hackage <$> Toml.reqKey "package", + GHC <$> Toml.reqKey "ghc-component" + ] + cvss <- Toml.reqKey "cvss" os <- Toml.optKey "os" arch <- Toml.optKey "arch" decls <- maybe [] Map.toList <$> Toml.optKey "declarations" @@ -187,7 +194,9 @@ instance Toml.ToTable Affected where [ "declarations" Toml..= asTable (affectedDeclarations x) | not (null (affectedDeclarations x))] where ecosystem = case affectedComponentIdentifier x of - Hackage pkg -> ["package" Toml..= pkg] + Repository repoUrl repoName pkg + | affectedComponentIdentifier x == hackage pkg -> ["package" Toml..= pkg] + | otherwise -> ["repository-url" Toml..= repoUrl, "repository-name" Toml..= repoName, "package" Toml..= pkg] GHC c -> ["ghc-component" Toml..= c] asTable kvs = Map.fromList [(T.unpack k, v) | (k,v) <- kvs] @@ -337,6 +346,20 @@ instance Toml.FromValue CVSS.CVSS where instance Toml.ToValue CVSS.CVSS where toValue = Toml.toValue . CVSS.cvssVectorString +instance Toml.ToValue PackageName where + toValue = Toml.toValue . unPackageName + +instance Toml.FromValue PackageName where + fromValue = fmap mkPackageName . Toml.fromValue + +deriving newtype instance Toml.ToValue RepositoryURL + +deriving newtype instance Toml.FromValue RepositoryURL + +deriving newtype instance Toml.ToValue RepositoryName + +deriving newtype instance Toml.FromValue RepositoryName + -- | A solution to an awkward problem: how to delete the TOML -- block. We parse into this type to get the source range of -- the first block element. We can use it to delete the lines diff --git a/code/hsec-tools/src/Security/Advisories/Generate/HTML.hs b/code/hsec-tools/src/Security/Advisories/Generate/HTML.hs index 6fd492c8..9809ce8b 100644 --- a/code/hsec-tools/src/Security/Advisories/Generate/HTML.hs +++ b/code/hsec-tools/src/Security/Advisories/Generate/HTML.hs @@ -9,7 +9,11 @@ module Security.Advisories.Generate.HTML where import Control.Monad (forM_) +import Control.Monad.Trans.Resource (ResourceT) import qualified Data.ByteString.Char8 as BS8 +import qualified Data.Conduit as Conduit +import qualified Data.Conduit.Binary as ConduitBinary +import Data.Default (def) import Data.List (sortOn) import Data.List.Extra (groupSort) import qualified Data.Map.Strict as Map @@ -19,35 +23,29 @@ import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as BSE import qualified Data.Text.IO as T -import Data.Time (UTCTime, formatTime, defaultTimeLocale) -import System.Directory (createDirectoryIfMissing) -import System.Exit (exitFailure) -import System.FilePath ((>), takeDirectory) -import System.IO (hPrint, hPutStrLn, stderr) -import System.IO.Unsafe (unsafePerformIO) - -import Control.Monad.Trans.Resource (ResourceT) -import qualified Data.Conduit as Conduit -import qualified Data.Conduit.Binary as ConduitBinary -import Data.Default (def) +import Data.Time (UTCTime, defaultTimeLocale, formatTime) import Distribution.Pretty (prettyShow) import Distribution.Types.VersionRange (earlierVersion, intersectVersionRanges, orLaterVersion) import Lucid import Refined (refineTH) -import qualified Text.Atom.Types as Feed +import qualified Security.Advisories as Advisories +import Security.Advisories.Core.Advisory (ComponentIdentifier (..), ghcComponentToText) +import Security.Advisories.Filesystem (listAdvisories) +import Security.Advisories.Generate.TH (readDirFilesTH) +import qualified Security.OSV as OSV +import System.Directory (createDirectoryIfMissing) +import System.Exit (exitFailure) +import System.FilePath (takeDirectory, (>)) +import System.IO (hPrint, hPutStrLn, stderr) +import System.IO.Unsafe (unsafePerformIO) import qualified Text.Atom.Conduit.Render as FeedExport +import qualified Text.Atom.Types as Feed import Text.Pandoc (runIOorExplode) import Text.Pandoc.Writers (writeHtml5String) import qualified Text.XML.Stream.Render as ConduitXML import qualified URI.ByteString as URI import Validation (Validation (..)) -import qualified Security.Advisories as Advisories -import Security.Advisories.Filesystem (listAdvisories) -import Security.Advisories.Generate.TH (readDirFilesTH) -import Security.Advisories.Core.Advisory (ComponentIdentifier (..), ghcComponentToText) -import qualified Security.OSV as OSV - -- * Actions renderAdvisoriesIndex :: FilePath -> FilePath -> IO () @@ -134,7 +132,7 @@ listByDates advisories = packageName :: AffectedPackageR -> Text packageName af = case ecosystem af of - Hackage n -> n + Repository _ repoName n -> "@" <> Advisories.unRepositoryName repoName <> "/" <> T.pack (Advisories.unPackageName n) GHC c -> "ghc:" <> ghcComponentToText c listByPackages :: [AdvisoryR] -> Html () @@ -164,7 +162,7 @@ listByPackages advisories = tbody_ $ do let sortedAdvisories = - sortOn (Down . advisoryId . fst) perPackageAdvisory + sortOn (Down . advisoryId . fst) perPackageAdvisory forM_ sortedAdvisories $ \(advisory, package) -> do tr_ $ do td_ [class_ "advisory-id"] $ @@ -178,7 +176,7 @@ indexDescription :: Html () indexDescription = div_ [class_ "description"] $ do p_ "The Haskell Security Advisory Database is a repository of security advisories filed against packages published via Hackage." - p_ $ do + p_ $ do "It is generated from " a_ [href_ "https://github.com/haskell/security-advisories/", target_ "_blank", rel_ "noopener noreferrer"] "Haskell Security Advisory Database" ". " @@ -204,7 +202,7 @@ renderAdvisory advisory = dt_ "CAPECs" placeholderWhenEmptyOr (Advisories.advisoryCAPECs advisory) $ \capecs -> forM_ capecs $ \(Advisories.CAPEC capec) -> - dd_ [] $ a_ [href_ $ "https://capec.mitre.org/data/definitions/" <> T.pack (show capec) <> ".html"] $ toHtml $ show capec + dd_ [] $ a_ [href_ $ "https://capec.mitre.org/data/definitions/" <> T.pack (show capec) <> ".html"] $ toHtml $ show capec dt_ "CWEs" placeholderWhenEmptyOr (Advisories.advisoryCWEs advisory) $ \cwes -> forM_ cwes $ \(Advisories.CWE cwe) -> @@ -223,9 +221,17 @@ renderAdvisory advisory = h4_ [] "Affected" forM_ (Advisories.advisoryAffected advisory) $ \affected -> do h5_ [] $ - case Advisories.affectedComponentIdentifier affected of - Hackage package -> a_ [href_ $ "https://hackage.haskell.org/package/" <> package] $ code_ [] $ toHtml package - GHC component -> code_ [] $ toHtml $ Advisories.ghcComponentToText component + let (url, title) = + case Advisories.affectedComponentIdentifier affected of + Repository repoUrl repoName package -> + ( Advisories.unRepositoryURL repoUrl <> "/package/" <> T.pack (Advisories.unPackageName package), + "@" <> Advisories.unRepositoryName repoName <> "/" <> T.pack (Advisories.unPackageName package) + ) + GHC component -> + ( "https://gitlab.haskell.org/ghc/ghc", + Advisories.ghcComponentToText component + ) + in a_ [href_ url] $ code_ [] $ toHtml title dl_ [] $ do dt_ "CVSS" @@ -238,9 +244,9 @@ renderAdvisory advisory = T.pack $ prettyShow $ let introducedVersionRange = orLaterVersion $ Advisories.affectedVersionRangeIntroduced affectedVersionRange - in case Advisories.affectedVersionRangeFixed affectedVersionRange of - Nothing -> introducedVersionRange - Just fixedVersion -> introducedVersionRange `intersectVersionRanges` earlierVersion fixedVersion + in case Advisories.affectedVersionRangeFixed affectedVersionRange of + Nothing -> introducedVersionRange + Just fixedVersion -> introducedVersionRange `intersectVersionRanges` earlierVersion fixedVersion forM_ (Advisories.affectedArchitectures affected) $ \architectures -> do dt_ "Architectures" dd_ [] $ toHtml $ T.intercalate ", " $ T.toLower . T.pack . show <$> architectures @@ -329,53 +335,53 @@ toAdvisoryR x = feed :: [Advisories.Advisory] -> Feed.AtomFeed feed advisories = Feed.AtomFeed - { Feed.feedAuthors = [ hsrt ] - , Feed.feedCategories = [] - , Feed.feedContributors = [] - , Feed.feedEntries = toEntry <$> advisories - , Feed.feedGenerator = Nothing - , Feed.feedIcon = Nothing - , Feed.feedId = "73b10e73-16bc-4bf2-a56c-ad7d09213e45" - , Feed.feedLinks = [ - Feed.AtomLink - { Feed.linkHref = toAtomURI atomFeedUrl - , Feed.linkRel = "self" - , Feed.linkType = "" - , Feed.linkLang = "" - , Feed.linkTitle = "" - , Feed.linkLength = "" + { Feed.feedAuthors = [hsrt], + Feed.feedCategories = [], + Feed.feedContributors = [], + Feed.feedEntries = toEntry <$> advisories, + Feed.feedGenerator = Nothing, + Feed.feedIcon = Nothing, + Feed.feedId = "73b10e73-16bc-4bf2-a56c-ad7d09213e45", + Feed.feedLinks = + [ Feed.AtomLink + { Feed.linkHref = toAtomURI atomFeedUrl, + Feed.linkRel = "self", + Feed.linkType = "", + Feed.linkLang = "", + Feed.linkTitle = "", + Feed.linkLength = "" } - ] - , Feed.feedLogo = Nothing - , Feed.feedRights = Nothing - , Feed.feedSubtitle = Nothing - , Feed.feedTitle = Feed.AtomPlainText Feed.TypeText "Haskell Security Advisory DB" - , Feed.feedUpdated = maximum $ Advisories.advisoryModified <$> advisories + ], + Feed.feedLogo = Nothing, + Feed.feedRights = Nothing, + Feed.feedSubtitle = Nothing, + Feed.feedTitle = Feed.AtomPlainText Feed.TypeText "Haskell Security Advisory DB", + Feed.feedUpdated = maximum $ Advisories.advisoryModified <$> advisories } where toEntry advisory = Feed.AtomEntry - { Feed.entryAuthors = [hsrt] - , Feed.entryCategories = [] - , Feed.entryContent = Just $ Feed.AtomContentInlineText Feed.TypeHTML $ Advisories.advisoryHtml advisory - , Feed.entryContributors = [] - , Feed.entryId = T.pack $ Advisories.printHsecId $ Advisories.advisoryId advisory - , Feed.entryLinks = [ - Feed.AtomLink - { Feed.linkHref = toAtomURI $ toUrl advisory - , Feed.linkRel = "alternate" - , Feed.linkType = "" - , Feed.linkLang = "" - , Feed.linkTitle = "" - , Feed.linkLength = "" - } - ] - , Feed.entryPublished = Just $ Advisories.advisoryPublished advisory - , Feed.entryRights = Nothing - , Feed.entrySource = Nothing - , Feed.entrySummary = Nothing - , Feed.entryTitle = Feed.AtomPlainText Feed.TypeText $ mkTitle advisory - , Feed.entryUpdated = Advisories.advisoryModified advisory + { Feed.entryAuthors = [hsrt], + Feed.entryCategories = [], + Feed.entryContent = Just $ Feed.AtomContentInlineText Feed.TypeHTML $ Advisories.advisoryHtml advisory, + Feed.entryContributors = [], + Feed.entryId = T.pack $ Advisories.printHsecId $ Advisories.advisoryId advisory, + Feed.entryLinks = + [ Feed.AtomLink + { Feed.linkHref = toAtomURI $ toUrl advisory, + Feed.linkRel = "alternate", + Feed.linkType = "", + Feed.linkLang = "", + Feed.linkTitle = "", + Feed.linkLength = "" + } + ], + Feed.entryPublished = Just $ Advisories.advisoryPublished advisory, + Feed.entryRights = Nothing, + Feed.entrySource = Nothing, + Feed.entrySummary = Nothing, + Feed.entryTitle = Feed.AtomPlainText Feed.TypeText $ mkTitle advisory, + Feed.entryUpdated = Advisories.advisoryModified advisory } mkTitle advisory = @@ -392,11 +398,11 @@ feed advisories = . BSE.encodeUtf8 hsrt = - Feed.AtomPerson - { Feed.personName = $$(refineTH "Haskell Security Response Team") - , Feed.personEmail = "security-advisories@haskell.org" - , Feed.personUri = Nothing - } + Feed.AtomPerson + { Feed.personName = $$(refineTH "Haskell Security Response Team"), + Feed.personEmail = "security-advisories@haskell.org", + Feed.personUri = Nothing + } renderFeed :: [Advisories.Advisory] -> Conduit.ConduitT () BS8.ByteString (ResourceT IO) () renderFeed = diff --git a/code/hsec-tools/src/Security/Advisories/Generate/Snapshot.hs b/code/hsec-tools/src/Security/Advisories/Generate/Snapshot.hs index 330290f5..23b9ce3f 100644 --- a/code/hsec-tools/src/Security/Advisories/Generate/Snapshot.hs +++ b/code/hsec-tools/src/Security/Advisories/Generate/Snapshot.hs @@ -74,7 +74,7 @@ createSnapshot src dst = do advisoryFilename = takeFileName p legacyComponentFile = \case - Hackage pkg -> dst > "hackage" > T.unpack pkg > advisoryFilename + Repository _ repoName pkg -> dst > T.unpack (unRepositoryName repoName) > unPackageName pkg > advisoryFilename GHC comp -> dst > "ghc" > T.unpack (ghcComponentToText comp) > advisoryFilename forM_ targetFiles $ \targetFile -> do hPutStrLn stderr $ " * Writing it to '" <> targetFile <> "'" diff --git a/code/hsec-tools/test/Spec/FormatSpec.hs b/code/hsec-tools/test/Spec/FormatSpec.hs index 2ee45e59..29d92015 100644 --- a/code/hsec-tools/test/Spec/FormatSpec.hs +++ b/code/hsec-tools/test/Spec/FormatSpec.hs @@ -78,7 +78,8 @@ genAffected = genComponentIdentifier :: Gen.Gen ComponentIdentifier genComponentIdentifier = Gen.choice $ - [ Hackage <$> genText + [ Repository <$> (RepositoryURL <$> genText) <*> (RepositoryName <$> genText) <*> (mkPackageName . T.unpack <$> genText) + , hackage . mkPackageName . T.unpack <$> genText , GHC <$> Gen.enumBounded ] diff --git a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_GHC.md b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_GHC.md new file mode 100644 index 00000000..07dd39d8 --- /dev/null +++ b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_GHC.md @@ -0,0 +1,32 @@ +```toml + +[advisory] +id = "HSEC-0000-0000" +cwe = [] +keywords = ["example", "freeform", "keywords"] +aliases = ["CVE-2022-XXXX"] +related = ["CVE-2022-YYYY", "CVE-2022-ZZZZ"] + +[[affected]] +ghc-component = "ghci" +cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + +[[affected.versions]] +introduced = "1.0.8" +fixed = "1.1" +[[affected.versions]] +introduced = "1.1.2" + +[[references]] +type = "ARTICLE" +url = "https://example.com" +``` + +# Advisory Template - Title Goes Here + +This is an example template. + + * Markdown + * TOML "front matter". + + > Acme Broken. diff --git a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY.md.golden b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_GHC.md.golden similarity index 96% rename from code/hsec-tools/test/golden/EXAMPLE_ADVISORY.md.golden rename to code/hsec-tools/test/golden/EXAMPLE_ADVISORY_GHC.md.golden index 62dda0c3..ea2b7d5a 100644 --- a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY.md.golden +++ b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_GHC.md.golden @@ -17,7 +17,7 @@ Right ] , advisoryAffected = [ Affected - { affectedComponentIdentifier = Hackage "package-name" + { affectedComponentIdentifier = GHC GHCi , affectedCVSS = CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H , affectedVersions = [ AffectedVersionRange @@ -117,7 +117,7 @@ Right related = ["CVE-2022-YYYY", "CVE-2022-ZZZZ"] [[affected]] - package = "package-name" + ghc-component = "ghci" cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" [[affected.versions]] @@ -186,8 +186,8 @@ Model ] Nothing ] , affectedPackage = Package - { packageName = "package-name" - , packageEcosystem = "Hackage" + { packageName = "ghci" + , packageEcosystem = "GHC" , packagePurl = Nothing } , affectedSeverity = @@ -209,8 +209,8 @@ Model "affected": [ { "package": { - "ecosystem": "Hackage", - "name": "package-name" + "ecosystem": "GHC", + "name": "ghci" }, "ranges": [ { diff --git a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY.md b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_HACKAGE.md similarity index 100% rename from code/hsec-tools/test/golden/EXAMPLE_ADVISORY.md rename to code/hsec-tools/test/golden/EXAMPLE_ADVISORY_HACKAGE.md diff --git a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_HACKAGE.md.golden b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_HACKAGE.md.golden new file mode 100644 index 00000000..24c402df --- /dev/null +++ b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_HACKAGE.md.golden @@ -0,0 +1,266 @@ +Right + ( Advisory + { advisoryId = HSEC-0000-0000 + , advisoryModified = 1970-01-01 00:00:00 UTC + , advisoryPublished = 1970-01-01 00:00:00 UTC + , advisoryCAPECs = [] + , advisoryCWEs = [] + , advisoryKeywords = + [ "example" + , "freeform" + , "keywords" + ] + , advisoryAliases = [ "CVE-2022-XXXX" ] + , advisoryRelated = + [ "CVE-2022-YYYY" + , "CVE-2022-ZZZZ" + ] + , advisoryAffected = + [ Affected + { affectedComponentIdentifier = Repository + ( RepositoryURL + { unRepositoryURL = "https://hackage.haskell.org" } + ) + ( RepositoryName + { unRepositoryName = "hackage" } + ) + ( PackageName "package-name" ) + , affectedCVSS = CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + , affectedVersions = + [ AffectedVersionRange + { affectedVersionRangeIntroduced = mkVersion + [ 1 + , 0 + , 8 + ] + , affectedVersionRangeFixed = Just + ( mkVersion + [ 1 + , 1 + ] + ) + } + , AffectedVersionRange + { affectedVersionRangeIntroduced = mkVersion + [ 1 + , 1 + , 2 + ] + , affectedVersionRangeFixed = Nothing + } + ] + , affectedArchitectures = Nothing + , affectedOS = Nothing + , affectedDeclarations = [] + } + ] + , advisoryReferences = + [ Reference + { referencesType = ReferenceTypeArticle + , referencesUrl = "https://example.com" + } + ] + , advisoryPandoc = Pandoc + ( Meta + { unMeta = fromList [] } + ) + [ Header 1 + ( "" + , [] + , [] + ) + [ Str "Advisory" + , Space + , Str "Template" + , Space + , Str "-" + , Space + , Str "Title" + , Space + , Str "Goes" + , Space + , Str "Here" + ] + , Para + [ Str "This" + , Space + , Str "is" + , Space + , Str "an" + , Space + , Str "example" + , Space + , Str "template." + ] + , BulletList + [ + [ Plain + [ Str "Markdown" ] + ] + , + [ Plain + [ Str "TOML" + , Space + , Str ""front" + , Space + , Str "matter"." + ] + ] + ] + , BlockQuote + [ Para + [ Str "Acme" + , Space + , Str "Broken." + ] + ] + ] + , advisoryHtml = "
+ [advisory]
+ id = "HSEC-0000-0000"
+ cwe = []
+ keywords = ["example", "freeform", "keywords"]
+ aliases = ["CVE-2022-XXXX"]
+ related = ["CVE-2022-YYYY", "CVE-2022-ZZZZ"]
+
+ [[affected]]
+ package = "package-name"
+ cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
+
+ [[affected.versions]]
+ introduced = "1.0.8"
+ fixed = "1.1"
+ [[affected.versions]]
+ introduced = "1.1.2"
+
+ [[references]]
+ type = "ARTICLE"
+ url = "https://example.com"
+
+ This is an example template.
+++ " + , advisorySummary = "Advisory Template - Title Goes Here" + , advisoryDetails = "# Advisory Template - Title Goes Here + + This is an example template. + + * Markdown + * TOML "front matter". + + > Acme Broken. + " + } + ) +Model + { modelSchemaVersion = "1.5.0" + , modelId = "HSEC-0000-0000" + , modelModified = 1970-01-01 00:00:00 UTC + , modelPublished = Just 1970-01-01 00:00:00 UTC + , modelWithdrawn = Nothing + , modelAliases = [ "CVE-2022-XXXX" ] + , modelRelated = + [ "CVE-2022-YYYY" + , "CVE-2022-ZZZZ" + ] + , modelSummary = Just "Advisory Template - Title Goes Here" + , modelDetails = Just "# Advisory Template - Title Goes Here + + This is an example template. + + * Markdown + * TOML "front matter". + + > Acme Broken. + " + , modelSeverity = [] + , modelAffected = + [ Affected + { affectedRanges = + [ RangeEcosystem + [ EventIntroduced "1.0.8" + , EventFixed "1.1" + , EventIntroduced "1.1.2" + ] Nothing + ] + , affectedPackage = Package + { packageName = "package-name" + , packageEcosystem = "Hackage" + , packagePurl = Nothing + } + , affectedSeverity = + [ Severity CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H ] + , affectedEcosystemSpecific = Nothing + , affectedDatabaseSpecific = Nothing + } + ] + , modelReferences = + [ Reference + { referencesType = ReferenceTypeArticle + , referencesUrl = "https://example.com" + } + ] + , modelCredits = [] + , modelDatabaseSpecific = Nothing + } +{ + "affected": [ + { + "package": { + "ecosystem": "Hackage", + "name": "package-name" + }, + "ranges": [ + { + "events": [ + { + "introduced": "1.0.8" + }, + { + "fixed": "1.1" + }, + { + "introduced": "1.1.2" + } + ], + "type": "ECOSYSTEM" + } + ], + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "type": "CVSS_V3" + } + ] + } + ], + "aliases": [ + "CVE-2022-XXXX" + ], + "details": "# Advisory Template - Title Goes Here\n\nThis is an example template.\n\n * Markdown\n * TOML \"front matter\".\n\n > Acme Broken.\n", + "id": "HSEC-0000-0000", + "modified": "1970-01-01T00:00:00Z", + "published": "1970-01-01T00:00:00Z", + "references": [ + { + "type": "ARTICLE", + "url": "https://example.com" + } + ], + "related": [ + "CVE-2022-YYYY", + "CVE-2022-ZZZZ" + ], + "schema_version": "1.5.0", + "summary": "Advisory Template - Title Goes Here" +} + diff --git a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_REPO.md b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_REPO.md new file mode 100644 index 00000000..8c6c066b --- /dev/null +++ b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_REPO.md @@ -0,0 +1,34 @@ +```toml + +[advisory] +id = "HSEC-0000-0000" +cwe = [] +keywords = ["example", "freeform", "keywords"] +aliases = ["CVE-2022-XXXX"] +related = ["CVE-2022-YYYY", "CVE-2022-ZZZZ"] + +[[affected]] +repository-url = "https//hackage.example.org/" +repository-name = "example" +package = "package-name" +cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + +[[affected.versions]] +introduced = "1.0.8" +fixed = "1.1" +[[affected.versions]] +introduced = "1.1.2" + +[[references]] +type = "ARTICLE" +url = "https://example.com" +``` + +# Advisory Template - Title Goes Here + +This is an example template. + + * Markdown + * TOML "front matter". + + > Acme Broken. diff --git a/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_REPO.md.golden b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_REPO.md.golden new file mode 100644 index 00000000..e8f55951 --- /dev/null +++ b/code/hsec-tools/test/golden/EXAMPLE_ADVISORY_REPO.md.golden @@ -0,0 +1,268 @@ +Right + ( Advisory + { advisoryId = HSEC-0000-0000 + , advisoryModified = 1970-01-01 00:00:00 UTC + , advisoryPublished = 1970-01-01 00:00:00 UTC + , advisoryCAPECs = [] + , advisoryCWEs = [] + , advisoryKeywords = + [ "example" + , "freeform" + , "keywords" + ] + , advisoryAliases = [ "CVE-2022-XXXX" ] + , advisoryRelated = + [ "CVE-2022-YYYY" + , "CVE-2022-ZZZZ" + ] + , advisoryAffected = + [ Affected + { affectedComponentIdentifier = Repository + ( RepositoryURL + { unRepositoryURL = "https//hackage.example.org/" } + ) + ( RepositoryName + { unRepositoryName = "example" } + ) + ( PackageName "package-name" ) + , affectedCVSS = CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + , affectedVersions = + [ AffectedVersionRange + { affectedVersionRangeIntroduced = mkVersion + [ 1 + , 0 + , 8 + ] + , affectedVersionRangeFixed = Just + ( mkVersion + [ 1 + , 1 + ] + ) + } + , AffectedVersionRange + { affectedVersionRangeIntroduced = mkVersion + [ 1 + , 1 + , 2 + ] + , affectedVersionRangeFixed = Nothing + } + ] + , affectedArchitectures = Nothing + , affectedOS = Nothing + , affectedDeclarations = [] + } + ] + , advisoryReferences = + [ Reference + { referencesType = ReferenceTypeArticle + , referencesUrl = "https://example.com" + } + ] + , advisoryPandoc = Pandoc + ( Meta + { unMeta = fromList [] } + ) + [ Header 1 + ( "" + , [] + , [] + ) + [ Str "Advisory" + , Space + , Str "Template" + , Space + , Str "-" + , Space + , Str "Title" + , Space + , Str "Goes" + , Space + , Str "Here" + ] + , Para + [ Str "This" + , Space + , Str "is" + , Space + , Str "an" + , Space + , Str "example" + , Space + , Str "template." + ] + , BulletList + [ + [ Plain + [ Str "Markdown" ] + ] + , + [ Plain + [ Str "TOML" + , Space + , Str ""front" + , Space + , Str "matter"." + ] + ] + ] + , BlockQuote + [ Para + [ Str "Acme" + , Space + , Str "Broken." + ] + ] + ] + , advisoryHtml = "Acme Broken.
+
+ [advisory]
+ id = "HSEC-0000-0000"
+ cwe = []
+ keywords = ["example", "freeform", "keywords"]
+ aliases = ["CVE-2022-XXXX"]
+ related = ["CVE-2022-YYYY", "CVE-2022-ZZZZ"]
+
+ [[affected]]
+ repository-url = "https//hackage.example.org/"
+ repository-name = "example"
+ package = "package-name"
+ cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
+
+ [[affected.versions]]
+ introduced = "1.0.8"
+ fixed = "1.1"
+ [[affected.versions]]
+ introduced = "1.1.2"
+
+ [[references]]
+ type = "ARTICLE"
+ url = "https://example.com"
+
+ This is an example template.
+++ " + , advisorySummary = "Advisory Template - Title Goes Here" + , advisoryDetails = "# Advisory Template - Title Goes Here + + This is an example template. + + * Markdown + * TOML "front matter". + + > Acme Broken. + " + } + ) +Model + { modelSchemaVersion = "1.5.0" + , modelId = "HSEC-0000-0000" + , modelModified = 1970-01-01 00:00:00 UTC + , modelPublished = Just 1970-01-01 00:00:00 UTC + , modelWithdrawn = Nothing + , modelAliases = [ "CVE-2022-XXXX" ] + , modelRelated = + [ "CVE-2022-YYYY" + , "CVE-2022-ZZZZ" + ] + , modelSummary = Just "Advisory Template - Title Goes Here" + , modelDetails = Just "# Advisory Template - Title Goes Here + + This is an example template. + + * Markdown + * TOML "front matter". + + > Acme Broken. + " + , modelSeverity = [] + , modelAffected = + [ Affected + { affectedRanges = + [ RangeEcosystem + [ EventIntroduced "1.0.8" + , EventFixed "1.1" + , EventIntroduced "1.1.2" + ] Nothing + ] + , affectedPackage = Package + { packageName = "package-name" + , packageEcosystem = "example" + , packagePurl = Nothing + } + , affectedSeverity = + [ Severity CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H ] + , affectedEcosystemSpecific = Nothing + , affectedDatabaseSpecific = Nothing + } + ] + , modelReferences = + [ Reference + { referencesType = ReferenceTypeArticle + , referencesUrl = "https://example.com" + } + ] + , modelCredits = [] + , modelDatabaseSpecific = Nothing + } +{ + "affected": [ + { + "package": { + "ecosystem": "example", + "name": "package-name" + }, + "ranges": [ + { + "events": [ + { + "introduced": "1.0.8" + }, + { + "fixed": "1.1" + }, + { + "introduced": "1.1.2" + } + ], + "type": "ECOSYSTEM" + } + ], + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "type": "CVSS_V3" + } + ] + } + ], + "aliases": [ + "CVE-2022-XXXX" + ], + "details": "# Advisory Template - Title Goes Here\n\nThis is an example template.\n\n * Markdown\n * TOML \"front matter\".\n\n > Acme Broken.\n", + "id": "HSEC-0000-0000", + "modified": "1970-01-01T00:00:00Z", + "published": "1970-01-01T00:00:00Z", + "references": [ + { + "type": "ARTICLE", + "url": "https://example.com" + } + ], + "related": [ + "CVE-2022-YYYY", + "CVE-2022-ZZZZ" + ], + "schema_version": "1.5.0", + "summary": "Advisory Template - Title Goes Here" +} +Acme Broken.
+