a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
152 lines
5.4 KiB
Go
152 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// flowDaemonTool is the daemon-tool relay seam. It is indirected through a
|
|
// package var so tests can stub the daemon call (asserting the lowered
|
|
// tool + args) without a running daemon. Shared by `flow` and `taint`.
|
|
var flowDaemonTool = requireDaemonTool
|
|
|
|
// --- flow (flow_between) ----------------------------------------------------
|
|
|
|
var (
|
|
flowIndex string
|
|
flowFrom string
|
|
flowTo string
|
|
flowMaxDepth int
|
|
flowMaxPaths int
|
|
flowMinTier string
|
|
flowFormat string
|
|
)
|
|
|
|
var flowCmd = &cobra.Command{
|
|
Use: "flow",
|
|
Short: "Trace ranked dataflow paths between two symbols (flow_between)",
|
|
Long: `Walks the CPG-lite dataflow graph (value-flow / arg-of / returns-to edges)
|
|
forward from --from to --to and returns the ranked paths — the primitive that
|
|
answers "where does this value flow?". Pair with 'gortex taint' for the
|
|
pattern-driven sweep.
|
|
|
|
gortex flow --from pkg/a.go::Input --to pkg/b.go::Sink --max-depth 6
|
|
|
|
Requires a running daemon that tracks the repo.`,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
if flowFrom == "" || flowTo == "" {
|
|
return fmt.Errorf("--from and --to are required (source and sink symbol IDs)")
|
|
}
|
|
toolArgs := map[string]any{
|
|
"source_id": flowFrom,
|
|
"sink_id": flowTo,
|
|
}
|
|
if cmd.Flags().Changed("max-depth") {
|
|
toolArgs["max_depth"] = flowMaxDepth
|
|
}
|
|
if cmd.Flags().Changed("max-paths") {
|
|
toolArgs["max_paths"] = flowMaxPaths
|
|
}
|
|
if cmd.Flags().Changed("min-tier") {
|
|
toolArgs["min_tier"] = flowMinTier
|
|
}
|
|
return runFlowTool(cmd, "flow_between", toolArgs, flowFormat)
|
|
},
|
|
}
|
|
|
|
// --- taint (taint_paths) ----------------------------------------------------
|
|
|
|
var (
|
|
taintIndex string
|
|
taintSource string
|
|
taintSink string
|
|
taintMaxDepth int
|
|
taintLimit int
|
|
taintMinTier string
|
|
taintFormat string
|
|
)
|
|
|
|
var taintCmd = &cobra.Command{
|
|
Use: "taint",
|
|
Short: "Pattern-driven dataflow sweep from matching sources to sinks (taint_paths)",
|
|
Long: `Resolves every symbol matching --source and --sink, then walks the dataflow
|
|
graph to find paths between each pair — the security-audit primitive ("every
|
|
flow from os.Getenv to db.Query"). Pattern syntax: a bare token is a
|
|
case-insensitive substring on the symbol name; exact:Foo matches exactly;
|
|
path:dir/ filters by file-path prefix; kind:method restricts the node kind.
|
|
Combine clauses with spaces.
|
|
|
|
gortex taint --source 'path:handlers/' --sink 'exact:Exec' --limit 30
|
|
|
|
Requires a running daemon that tracks the repo.`,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
if taintSource == "" || taintSink == "" {
|
|
return fmt.Errorf("--source and --sink are required (source and sink patterns)")
|
|
}
|
|
toolArgs := map[string]any{
|
|
"source_pattern": taintSource,
|
|
"sink_pattern": taintSink,
|
|
}
|
|
if cmd.Flags().Changed("max-depth") {
|
|
toolArgs["max_depth"] = taintMaxDepth
|
|
}
|
|
if cmd.Flags().Changed("limit") {
|
|
toolArgs["limit"] = taintLimit
|
|
}
|
|
if cmd.Flags().Changed("min-tier") {
|
|
toolArgs["min_tier"] = taintMinTier
|
|
}
|
|
return runFlowTool(cmd, "taint_paths", toolArgs, taintFormat)
|
|
},
|
|
}
|
|
|
|
// runFlowTool calls a dataflow tool and renders the result. gcx/toon are printed
|
|
// verbatim (re-indenting would corrupt them); every other format pretty-prints
|
|
// the JSON via emitDaemonJSON. The repo path comes from the per-command --index.
|
|
func runFlowTool(cmd *cobra.Command, tool string, args map[string]any, format string) error {
|
|
repo := flowIndex
|
|
if tool == "taint_paths" {
|
|
repo = taintIndex
|
|
}
|
|
if format != "" {
|
|
args["format"] = format
|
|
}
|
|
raw, err := flowDaemonTool(repo, tool, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch format {
|
|
case "gcx", "toon":
|
|
fmt.Fprintln(cmd.OutOrStdout(), strings.TrimRight(string(raw), "\n"))
|
|
return nil
|
|
default:
|
|
return emitDaemonJSON(cmd, raw)
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
flowCmd.Flags().StringVar(&flowIndex, "index", ".", "repository path the daemon must track")
|
|
flowCmd.Flags().StringVar(&flowIndex, "repo", ".", "alias for --index")
|
|
flowCmd.Flags().StringVar(&flowFrom, "from", "", "source symbol node ID (source_id)")
|
|
flowCmd.Flags().StringVar(&flowTo, "to", "", "sink symbol node ID (sink_id)")
|
|
flowCmd.Flags().IntVar(&flowMaxDepth, "max-depth", 0, "maximum BFS hops (max_depth; default 8)")
|
|
flowCmd.Flags().IntVar(&flowMaxPaths, "max-paths", 0, "maximum paths to return (max_paths; default 10)")
|
|
flowCmd.Flags().StringVar(&flowMinTier, "min-tier", "", "minimum per-edge Origin tier to traverse (min_tier)")
|
|
flowCmd.Flags().StringVar(&flowFormat, "format", "json", "output / wire format: json|gcx|toon|text")
|
|
rootCmd.AddCommand(flowCmd)
|
|
|
|
taintCmd.Flags().StringVar(&taintIndex, "index", ".", "repository path the daemon must track")
|
|
taintCmd.Flags().StringVar(&taintIndex, "repo", ".", "alias for --index")
|
|
taintCmd.Flags().StringVar(&taintSource, "source", "", "source pattern (source_pattern)")
|
|
taintCmd.Flags().StringVar(&taintSink, "sink", "", "sink pattern (sink_pattern)")
|
|
taintCmd.Flags().IntVar(&taintMaxDepth, "max-depth", 0, "maximum BFS hops per pair (max_depth; default 8)")
|
|
taintCmd.Flags().IntVar(&taintLimit, "limit", 0, "maximum findings to return (default 20)")
|
|
taintCmd.Flags().StringVar(&taintMinTier, "min-tier", "", "minimum per-edge Origin tier to traverse (min_tier)")
|
|
taintCmd.Flags().StringVar(&taintFormat, "format", "json", "output / wire format: json|gcx|toon|text")
|
|
rootCmd.AddCommand(taintCmd)
|
|
}
|