commit bfc4d70a9dd50b478247454e14b4757955316cd5
parent 2549b3e1c405920476b40f28a9f21d340a4d257f
Author: Jared Tobin <jared@jtobin.io>
Date: Wed, 11 Feb 2026 22:03:02 +0400
feat: add --display-unknown flag to filter violation output
By default, only show secret violations (SecretBase, SecretIndex,
NonConstOffset). Unknown violations are hidden to reduce noise.
- Add -u/--display-unknown flag to show all violations
- Filter violations in both text and JSON output
- Show "(hidden): N unknown (use -u to show)" in summary when filtered
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
| M | app/Main.hs | | | 50 | ++++++++++++++++++++++++++++++++++++++++---------- |
1 file changed, 40 insertions(+), 10 deletions(-)
diff --git a/app/Main.hs b/app/Main.hs
@@ -19,12 +19,13 @@ import Options.Applicative
import System.Exit (exitFailure, exitSuccess)
data Options = Options
- { optInput :: !FilePath
- , optJson :: !Bool
- , optQuiet :: !Bool
- , optInterProc :: !Bool
- , optParseOnly :: !Bool
- , optTaintConfig :: !(Maybe FilePath)
+ { optInput :: !FilePath
+ , optJson :: !Bool
+ , optQuiet :: !Bool
+ , optInterProc :: !Bool
+ , optParseOnly :: !Bool
+ , optTaintConfig :: !(Maybe FilePath)
+ , optDisplayUnknown :: !Bool
} deriving (Eq, Show)
optParser :: Parser Options
@@ -60,6 +61,11 @@ optParser = Options
<> metavar "FILE"
<> help "JSON file with per-function taint policies"
))
+ <*> switch
+ ( long "display-unknown"
+ <> short 'u'
+ <> help "Display unknown violations (only secret shown by default)"
+ )
optInfo :: ParserInfo Options
optInfo = info (optParser <**> helper)
@@ -99,7 +105,7 @@ main = do
exitFailure
Right ar ->
if optJson opts
- then outputJson ar
+ then outputJson opts ar
else outputText opts ar
where
emptyConfig = TaintConfig Map.empty
@@ -112,12 +118,15 @@ main = do
where
hasConfig = not (Map.null (tcPolicies cfg))
-outputJson :: AuditResult -> IO ()
-outputJson ar = BL.putStrLn (encode (arViolations ar))
+outputJson :: Options -> AuditResult -> IO ()
+outputJson opts ar =
+ let vs = filterViolations opts (arViolations ar)
+ in BL.putStrLn (encode vs)
outputText :: Options -> AuditResult -> IO ()
outputText opts ar = do
- let vs = arViolations ar
+ let allVs = arViolations ar
+ vs = filterViolations opts allVs
mapM_ printViolation vs
if optQuiet opts
then pure ()
@@ -126,10 +135,31 @@ outputText opts ar = do
TIO.putStrLn $ "Lines checked: " <> T.pack (show (arLinesChecked ar))
TIO.putStrLn $ "Memory accesses: " <> T.pack (show (arMemoryAccesses ar))
TIO.putStrLn $ "Violations: " <> T.pack (show (length vs))
+ if not (optDisplayUnknown opts) && length vs < length allVs
+ then TIO.putStrLn $ " (hidden): "
+ <> T.pack (show (length allVs - length vs))
+ <> " unknown (use -u to show)"
+ else pure ()
if null vs
then exitSuccess
else exitFailure
+-- | Filter violations based on options.
+-- By default, only secret violations are shown.
+filterViolations :: Options -> [Violation] -> [Violation]
+filterViolations opts
+ | optDisplayUnknown opts = id
+ | otherwise = filter (isSecretViolation . vReason)
+
+-- | Check if a violation reason is secret (not unknown).
+isSecretViolation :: ViolationReason -> Bool
+isSecretViolation r = case r of
+ SecretBase _ -> True
+ SecretIndex _ -> True
+ UnknownBase _ -> False
+ UnknownIndex _ -> False
+ NonConstOffset -> True -- Treat as secret-level concern
+
printViolation :: Violation -> IO ()
printViolation v = TIO.putStrLn $
vSymbol v <> ":" <> T.pack (show (vLine v)) <> ": " <> reasonText (vReason v)