Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strict mode #1463

Merged
merged 11 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# FOSSA CLI Changelog

## 3.9.32

- `--strict`: Users can now enable strict mode for analysis. ([#1463](https://github.com/fossas/fossa-cli/pull/1463))

## 3.9.31

- Resolve an issue parsing toml configuration files. ([#1459](https://github.com/fossas/fossa-cli/pull/1459))
Expand Down
11 changes: 6 additions & 5 deletions docs/references/subcommands/analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ We support the following archive formats:

In addition to the [standard flags](#specifying-fossa-project-details), the analyze command supports the following additional strategy flags:

| Name | Description |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`--detect-vendored`](./analyze/detect-vendored.md) | Enable the vendored source identification engine. For more information, see the [C and C++ overview](../strategies/languages/c-cpp/c-cpp.md). |
| [`--detect-dynamic './some-binary`](./analyze/detect-dynamic.md) | Analyze the binary at the provided path for dynamically linked dependencies. For more information, see the [C and C++ overview](../strategies/languages/c-cpp/c-cpp.md). |
| [`--static-only-analysis`](../strategies/README.md#static-and-dynamic-strategies) | Do not use third-party tools when analyzing projects. |
| Name | Description |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`--detect-vendored`](./analyze/detect-vendored.md) | Enable the vendored source identification engine. For more information, see the [C and C++ overview](../strategies/languages/c-cpp/c-cpp.md). |
| [`--detect-dynamic './some-binary`](./analyze/detect-dynamic.md) | Analyze the binary at the provided path for dynamically linked dependencies. For more information, see the [C and C++ overview](../strategies/languages/c-cpp/c-cpp.md). |
| [`--static-only-analysis`](../strategies/README.md#static-and-dynamic-strategies) | Do not use third-party tools when analyzing projects. |
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved
| `--strict` | Enable strict analysis to enforce that the first analysis strategy within a [strategy type](../strategies/README.md#strategies-by-type) passes. Fallback strategies are not allowed in strict mode. |
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved


### Experimental Options
Expand Down
4 changes: 3 additions & 1 deletion integration-test/Analysis/FixtureUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module Analysis.FixtureUtils (

import App.Fossa.Analyze.Types (AnalyzeProject (analyzeProject))
import App.Fossa.Config.Analyze (ExperimentalAnalyzeConfig (ExperimentalAnalyzeConfig), GoDynamicTactic (GoModulesBasedTactic))
import App.Types (OverrideDynamicAnalysisBinary)
import App.Types (Mode (..), OverrideDynamicAnalysisBinary)
import Control.Carrier.Debug (IgnoreDebugC, ignoreDebug)
import Control.Carrier.Diagnostics (DiagnosticsC, runDiagnostics)
import Control.Carrier.Finally (FinallyC, runFinally)
Expand Down Expand Up @@ -118,6 +118,7 @@ type TestC m =
$ ReaderC AllFilters
$ ReaderC MavenScopeFilters
$ ReaderC ExperimentalAnalyzeConfig
$ ReaderC Mode
$ FinallyC
$ StackC
$ IgnoreTelemetryC m
Expand All @@ -134,6 +135,7 @@ testRunner f env =
& runReader (mempty :: AllFilters)
& runReader (MavenScopeIncludeFilters mempty)
& runReader (ExperimentalAnalyzeConfig Nothing GoModulesBasedTactic False)
& runReader (NonStrict :: Mode)
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved
& runFinally
& runStack
& withoutTelemetry
Expand Down
5 changes: 4 additions & 1 deletion src/App/Fossa/Analyze.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import App.Fossa.VSIDeps (analyzeVSIDeps)
import App.Types (
BaseDir (..),
FirstPartyScansFlag (..),
Mode (..),
OverrideDynamicAnalysisBinary,
ProjectRevision (..),
)
Expand Down Expand Up @@ -189,6 +190,7 @@ runDependencyAnalysis ::
, Has Stack sig m
, Has (Reader ExperimentalAnalyzeConfig) sig m
, Has (Reader MavenScopeFilters) sig m
, Has (Reader Mode) sig m
, Has (Reader AllFilters) sig m
, Has (Reader OverrideDynamicAnalysisBinary) sig m
, Has Telemetry sig m
Expand Down Expand Up @@ -294,6 +296,7 @@ analyze cfg = Diag.context "fossa-analyze" $ do
shouldAnalyzePathDependencies = resolvePathDependencies $ Config.experimental cfg
allowedTactics = Config.allowedTacticTypes cfg
withoutDefaultFilters = Config.withoutDefaultFilters cfg
-- strictMode = Config.strictMode cfg
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved

manualSrcUnits <-
Diag.errorBoundaryIO . diagToDebug $
Expand Down Expand Up @@ -361,7 +364,6 @@ analyze cfg = Diag.context "fossa-analyze" $ do
pure Nothing
else Diag.context "first-party-scans" . runStickyLogger SevInfo $ runFirstPartyScan basedir maybeApiOpts cfg
let firstPartyScanResults = join . resultToMaybe $ maybeFirstPartyScanResults

let discoveryFilters = if fromFlag NoDiscoveryExclusion noDiscoveryExclusion then mempty else filters
(projectScans, ()) <-
Diag.context "discovery/analysis tasks"
Expand All @@ -374,6 +376,7 @@ analyze cfg = Diag.context "fossa-analyze" $ do
. runReader (Config.mavenScopeFilterSet cfg)
. runReader discoveryFilters
. runReader (Config.overrideDynamicAnalysis cfg)
. runReader (Config.mode cfg)
$ do
runAnalyzers allowedTactics filters withoutDefaultFilters basedir Nothing
when (fromFlag UnpackArchives $ Config.unpackArchives cfg) $
Expand Down
3 changes: 3 additions & 0 deletions src/App/Fossa/Analyze/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import App.Fossa.Analyze.Project (ProjectResult)
import App.Fossa.Config.Analyze (ExperimentalAnalyzeConfig)
import App.Fossa.Lernie.Types (LernieResults)
import App.Fossa.Reachability.Types (SourceUnitReachability (..))
import App.Types (Mode)
import Control.Effect.Debug (Debug)
import Control.Effect.Diagnostics (Diagnostics, Has)
import Control.Effect.Lift (Lift)
Expand Down Expand Up @@ -44,6 +45,7 @@ type DiscoverTaskEffs sig m =
, Has Debug sig m
, Has (Reader ExperimentalAnalyzeConfig) sig m
, Has (Reader MavenScopeFilters) sig m
, Has (Reader Mode) sig m
, Has (Reader AllFilters) sig m
, Has Telemetry sig m
)
Expand All @@ -69,6 +71,7 @@ type AnalyzeStaticTaskEffs sig m =
, Has Debug sig m
, Has (Reader ExperimentalAnalyzeConfig) sig m
, Has (Reader MavenScopeFilters) sig m
, Has (Reader Mode) sig m
, Has (Reader AllFilters) sig m
, Has Telemetry sig m
)
Expand Down
20 changes: 16 additions & 4 deletions src/App/Fossa/Config/Analyze.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module App.Fossa.Config.Analyze (
GoDynamicTactic (..),
StaticOnlyTactics (..),
WithoutDefaultFilters (..),
StrictMode (..),
mkSubCommand,
loadConfig,
cliParser,
Expand Down Expand Up @@ -71,6 +72,7 @@ import App.Fossa.VSI.Types qualified as VSI
import App.Types (
BaseDir,
FirstPartyScansFlag (..),
Mode (..),
OverrideDynamicAnalysisBinary (..),
OverrideProject (OverrideProject),
ProjectMetadata (projectLabel),
Expand Down Expand Up @@ -135,6 +137,7 @@ data ForceFirstPartyScans = ForceFirstPartyScans deriving (Generic)
data ForceNoFirstPartyScans = ForceNoFirstPartyScans deriving (Generic)
data IgnoreOrgWideCustomLicenseScanConfigs = IgnoreOrgWideCustomLicenseScanConfigs deriving (Generic)
data StaticOnlyTactics = StaticOnlyTactics deriving (Generic)
data StrictMode = StrictMode deriving (Generic, Show)

data BinaryDiscovery = BinaryDiscovery deriving (Generic)
data IncludeAll = IncludeAll deriving (Generic)
Expand Down Expand Up @@ -226,6 +229,7 @@ data AnalyzeCliOpts = AnalyzeCliOpts
, analyzeCustomFossaDepsFile :: Maybe FilePath
, analyzeStaticOnlyTactics :: Flag StaticOnlyTactics
, analyzeWithoutDefaultFilters :: Flag WithoutDefaultFilters
, analyzeStrictMode :: Flag StrictMode
}
deriving (Eq, Ord, Show)

Expand Down Expand Up @@ -264,6 +268,7 @@ data AnalyzeConfig = AnalyzeConfig
, allowedTacticTypes :: AnalysisTacticTypes
, reachabilityConfig :: ReachabilityConfig
, withoutDefaultFilters :: Flag WithoutDefaultFilters
, mode :: Mode
}
deriving (Eq, Ord, Show, Generic)

Expand Down Expand Up @@ -333,6 +338,7 @@ cliParser =
<*> optional (strOption (applyFossaStyle <> long "fossa-deps-file" <> helpDoc fossaDepsFileHelp <> metavar "FILEPATH"))
<*> flagOpt StaticOnlyTactics (applyFossaStyle <> long "static-only-analysis" <> stringToHelpDoc "Only analyze the project using static strategies.")
<*> withoutDefaultFilterParser fossaAnalyzeDefaultFilterDocUrl
<*> flagOpt StrictMode (applyFossaStyle <> long "strict" <> stringToHelpDoc "Enables strict analysis to enforce that the first analysis strategy within a strategy type passes. Fallback strategies are not allowed in strict mode.")
where
fossaDepsFileHelp :: Maybe (Doc AnsiStyle)
fossaDepsFileHelp =
Expand All @@ -341,6 +347,7 @@ cliParser =
[ "Path to fossa-deps file including filename"
, boldItalicized "Default:" <> " fossa-deps.{yaml|yml|json}"
]

branchHelp :: Maybe (Doc AnsiStyle)
branchHelp =
Just . formatDoc $
Expand Down Expand Up @@ -499,7 +506,7 @@ mergeStandardOpts maybeConfig envvars cliOpts@AnalyzeCliOpts{..} = do
revisionData =
collectRevisionData' basedir maybeConfig WriteOnly $
OverrideProject (optProjectName commons) (optProjectRevision commons) (analyzeBranch)
modeOpts = collectModeOptions cliOpts
vsiModeOpts = collectVsiModeOptions cliOpts
filters = collectFilters maybeConfig cliOpts
mavenScopeFilters = collectMavenScopeFilters maybeConfig
experimentalCfgs = collectExperimental maybeConfig cliOpts
Expand All @@ -512,6 +519,10 @@ mergeStandardOpts maybeConfig envvars cliOpts@AnalyzeCliOpts{..} = do
then StaticOnly
else Any
reachabilityConfig = collectReachabilityOptions maybeConfig
mode =
if fromFlag StrictMode analyzeStrictMode
then Strict
else NonStrict

firstPartyScansFlag <-
case (fromFlag ForceFirstPartyScans analyzeForceFirstPartyScans, fromFlag ForceNoFirstPartyScans analyzeForceNoFirstPartyScans) of
Expand All @@ -525,7 +536,7 @@ mergeStandardOpts maybeConfig envvars cliOpts@AnalyzeCliOpts{..} = do
<*> pure logSeverity
<*> scanDestination
<*> revisionData
<*> modeOpts
<*> vsiModeOpts
<*> filters
<*> mavenScopeFilters
<*> pure experimentalCfgs
Expand All @@ -541,6 +552,7 @@ mergeStandardOpts maybeConfig envvars cliOpts@AnalyzeCliOpts{..} = do
<*> pure allowedTacticType
<*> resolveReachabilityOptions reachabilityConfig
<*> pure analyzeWithoutDefaultFilters
<*> pure mode

collectMavenScopeFilters ::
( Has Diagnostics sig m
Expand Down Expand Up @@ -643,14 +655,14 @@ collectScanDestination maybeCfgFile envvars AnalyzeCliOpts{..} =
when (length (projectLabel metaMerged) > 5) $ fatalText "Projects are only allowed to have 5 associated project labels"
pure $ UploadScan apiOpts metaMerged

collectModeOptions ::
collectVsiModeOptions ::
( Has Diagnostics sig m
, Has (Lift IO) sig m
, Has ReadFS sig m
) =>
AnalyzeCliOpts ->
m VSIModeOptions
collectModeOptions AnalyzeCliOpts{..} = do
collectVsiModeOptions AnalyzeCliOpts{..} = do
assertionDir <- traverse validateDir analyzeAssertMode
resolvedDynamicLinkTarget <- traverse validateExists analyzeDynamicLinkTarget
pure
Expand Down
4 changes: 4 additions & 0 deletions src/App/Fossa/Container/Sources/DockerArchive.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import App.Fossa.Analyze.Types (
import App.Fossa.Config.Analyze (ExperimentalAnalyzeConfig (ExperimentalAnalyzeConfig), GoDynamicTactic (GoModulesBasedTactic), WithoutDefaultFilters (..))
import App.Fossa.Container.Sources.Discovery (layerAnalyzers, renderLayerTarget)
import App.Fossa.Container.Sources.JarAnalysis (analyzeContainerJars)
import App.Types (Mode (..))
import Codec.Archive.Tar.Index (TarEntryOffset)
import Container.Docker.Manifest (getImageDigest, getRepoTags)
import Container.OsRelease (OsInfo (nameId, version), getOsInfo)
Expand Down Expand Up @@ -202,6 +203,7 @@ analyzeLayer systemDepsOnly filters withoutDefaultFilters capabilities osInfo la
runTarballReadFSIO layerFs tarball
. runReader noExperimental
. runReader noMavenScopeFilters
. runReader NonStrict
. Diag.context "discovery/analysis tasks"
. runOutput @DiscoveredProjectScan
. runStickyLogger SevInfo
Expand Down Expand Up @@ -256,6 +258,7 @@ runDependencyAnalysis ::
, Has (Output DiscoveredProjectScan) sig m
, Has (Reader ExperimentalAnalyzeConfig) sig m
, Has (Reader MavenScopeFilters) sig m
, Has (Reader Mode) sig m
, Has (Reader AllFilters) sig m
, Has Stack sig m
, Has Telemetry sig m
Expand Down Expand Up @@ -373,6 +376,7 @@ listTargetLayer capabilities osInfo layerFs tarball layerType = do
False -- Targets are not impacted by path dependencies.
)
. runReader (MavenScopeIncludeFilters mempty)
. runReader (NonStrict :: Mode)
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved
. runReader (mempty :: AllFilters)
$ run
where
Expand Down
4 changes: 3 additions & 1 deletion src/App/Fossa/ListTargets.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import App.Fossa.Config.ListTargets (
mkSubCommand,
)
import App.Fossa.Subcommand (SubCommand)
import App.Types (BaseDir (..))
import App.Types (BaseDir (..), Mode (..))
import Control.Carrier.AtomicCounter (
AtomicCounter,
Has,
Expand Down Expand Up @@ -83,6 +83,7 @@ listTargetsMain ListTargetsConfig{..} = do
. runReader experimental
-- `fossa list-targets` does not support maven scope filters.
. runReader (MavenScopeIncludeFilters mempty)
. runReader (NonStrict)
-- The current version of `fossa list-targets` does not support filters.
-- TODO: support both discovery and post-discovery filtering.
. runReader (mempty :: AllFilters)
Expand All @@ -99,6 +100,7 @@ runAll ::
, Has Stack sig m
, Has (Reader ExperimentalAnalyzeConfig) sig m
, Has (Reader MavenScopeFilters) sig m
, Has (Reader Mode) sig m
, Has (Reader AllFilters) sig m
, Has Telemetry sig m
) =>
Expand Down
9 changes: 9 additions & 0 deletions src/App/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module App.Types (
ReleaseGroupProjectRevision (..),
ReleaseGroupReleaseRevision (..),
ComponentUploadFileType (..),
Mode (..),
uploadFileTypeToFetcherName,
) where

Expand Down Expand Up @@ -208,3 +209,11 @@ data FirstPartyScansFlag = FirstPartyScansOnFromFlag | FirstPartyScansOffFromFla

instance ToJSON FirstPartyScansFlag where
toEncoding = genericToEncoding defaultOptions

data Mode
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved
= Strict
| NonStrict
deriving (Eq, Ord, Show, Generic)

instance ToJSON Mode where
toEncoding = genericToEncoding defaultOptions
10 changes: 9 additions & 1 deletion src/App/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ module App.Util (
ancestryDirect,
validateDir,
validateFile,
guardStrictMode,
FileAncestry (..),
) where

import App.Types
import Control.Algebra (Has)
import Control.Carrier.Diagnostics (Diagnostics, fatalText)
import Control.Carrier.Diagnostics (Diagnostics)
import Control.Effect.Diagnostics (fatalText)
import Control.Monad (unless)
import Data.String.Conversion (ToText (..))
import GHC.Generics (Generic)
Expand Down Expand Up @@ -51,3 +53,9 @@ ancestryDerived :: Has Diagnostics sig m => FileAncestry -> Path Abs Dir -> Path
ancestryDerived parent dir file = do
rel <- ancestryDirect dir file
pure $ fileAncestryPath parent </> rel

-- | Guards analysis strategies depending on the mode. On strict mode, emit an error to end the chain of diagnostics to prevent
-- other strategies to be executed. On non-strict mode, allow fallback strategies to be executed.
guardStrictMode :: Has Diagnostics sig m => Mode -> m a -> m a
guardStrictMode Strict _ = fatalText "Strict mode enabled, skipping other strategies"
guardStrictMode NonStrict action = action
21 changes: 14 additions & 7 deletions src/Strategy/Bundler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import App.Fossa.Analyze.LicenseAnalyze (
LicenseAnalyzeProject (licenseAnalyzeProject),
)
import App.Fossa.Analyze.Types (AnalyzeProject (analyzeProjectStaticOnly), analyzeProject)
import App.Types (Mode (..))
import App.Util (guardStrictMode)
import Control.Effect.Diagnostics (
Diagnostics,
context,
Expand All @@ -21,7 +23,7 @@ import Control.Effect.Diagnostics (
(<||>),
)
import Control.Effect.Diagnostics qualified as Diag
import Control.Effect.Reader (Reader)
import Control.Effect.Reader (Reader, ask)
import Data.Aeson (ToJSON)
import Data.Glob as Glob (toGlob, (</>))
import Data.Text (isSuffixOf)
Expand Down Expand Up @@ -119,8 +121,10 @@ mkProject project =
, projectData = project
}

getDeps :: (Has Exec sig m, Has ReadFS sig m, Has Diagnostics sig m) => BundlerProject -> m DependencyResults
getDeps project = analyzeGemfileLock project <||> context "Bundler" (analyzeBundleShow project)
getDeps :: (Has Exec sig m, Has ReadFS sig m, Has Diagnostics sig m, Has (Reader Mode) sig m) => BundlerProject -> m DependencyResults
JeffreyHuynh1 marked this conversation as resolved.
Show resolved Hide resolved
getDeps project = do
mode <- ask
analyzeGemfileLock project <||> guardStrictMode mode (context "Bundler" (analyzeBundleShow project))

analyzeBundleShow :: (Has Exec sig m, Has Diagnostics sig m) => BundlerProject -> m DependencyResults
analyzeBundleShow project = do
Expand All @@ -132,10 +136,13 @@ analyzeBundleShow project = do
, dependencyManifestFiles = maybe [bundlerGemfile project] pure (bundlerGemfileLock project)
}

analyzeGemfileLock :: (Has ReadFS sig m, Has Diagnostics sig m) => BundlerProject -> m DependencyResults
analyzeGemfileLock project =
warnOnErr AllDirectDeps
. warnOnErr MissingEdges
analyzeGemfileLock :: (Has ReadFS sig m, Has Diagnostics sig m, Has (Reader Mode) sig m) => BundlerProject -> m DependencyResults
analyzeGemfileLock project = do
mode <- ask
let errorInfo = case mode of
Strict -> id
NonStrict -> warnOnErr AllDirectDeps . warnOnErr MissingEdges
errorInfo
. errCtx (BundlerMissingLockFileCtx $ bundlerGemfile project)
. errHelp BundlerMissingLockFileHelp
. errDoc bundlerLockFileRationaleUrl
Expand Down
Loading
Loading