Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
16 changes: 16 additions & 0 deletions .github/workflows/release-cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,22 @@ jobs:
GORELEASER_CURRENT_TAG: v${{ steps.release_version.outputs.RELEASE_VERSION }}
HOMEBREW_TAP_OWNER: ${{ github.repository_owner }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
-
name: Bundle go-ssa-server into release archives
if: ${{ steps.release_version.outputs.status == 'succeeded' }}
working-directory: cli
env:
GH_TOKEN: ${{ secrets.SEQRA_GITHUB_TOKEN }}
run: |
bash scripts/bundle-go-server.sh dist ${{ github.repository }}
RELEASE_TAG="v${{ steps.release_version.outputs.RELEASE_VERSION }}"
# Re-upload the modified "opentaint" archives. The "opentaint-full"
# archives are re-uploaded by the JRE step, which runs after this one.
for archive in dist/opentaint_*.tar.gz dist/opentaint_*.zip; do
[ -f "$archive" ] || continue
echo "Re-uploading $archive..."
gh release upload "$RELEASE_TAG" "$archive" --clobber
done
-
name: Bundle JREs into release archives
if: ${{ steps.release_version.outputs.status == 'succeeded' }}
Expand Down
23 changes: 22 additions & 1 deletion .github/workflows/update-cli-versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@ on:
description: 'Rules version tag (e.g. rules/v0.1.0). Leave empty for latest.'
required: false
type: string
go_server_version:
description: 'Go server version tag (e.g. go-server/2026.03.19.adeb616). Leave empty for latest.'
required: false
type: string

workflow_run:
workflows:
- Publish Analyzer and Models
- Publish Autobuilder
- Release Rules
- Publish Go Server
branches: [main]
types: [completed]

Expand Down Expand Up @@ -66,19 +71,26 @@ jobs:
RULES=$(git tag -l "rules/v[0-9]*.[0-9]*.[0-9]*" --sort=-v:refname | head -1)
fi

GO_SERVER="${{ inputs.go_server_version }}"
if [ -z "$GO_SERVER" ]; then
GO_SERVER=$(git tag -l "go-server/[0-9]*" --sort=-creatordate | head -1)
fi

if [ -z "$ANALYZER" ] || [ -z "$AUTOBUILDER" ] || [ -z "$RULES" ]; then
echo "::error::Failed to resolve versions (analyzer=${ANALYZER:-<empty>}, autobuilder=${AUTOBUILDER:-<empty>}, rules=${RULES:-<empty>})"
echo "::error::Failed to resolve versions (analyzer=${ANALYZER:-<empty>}, autobuilder=${AUTOBUILDER:-<empty>}, rules=${RULES:-<empty>}, go-server=${GO_SERVER:-<empty>})"
exit 1
fi

echo "analyzer=$ANALYZER" >> "$GITHUB_OUTPUT"
echo "autobuilder=$AUTOBUILDER" >> "$GITHUB_OUTPUT"
echo "rules=$RULES" >> "$GITHUB_OUTPUT"
echo "go_server=$GO_SERVER" >> "$GITHUB_OUTPUT"

echo "Resolved versions:"
echo " Analyzer: $ANALYZER"
echo " Autobuilder: $AUTOBUILDER"
echo " Rules: $RULES"
echo " Go server: $GO_SERVER"

- name: Read current versions
id: current
Expand All @@ -87,6 +99,9 @@ jobs:
echo "analyzer=$(grep '^analyzer:' cli/internal/globals/versions.yaml | awk '{print $2}' | tr -d '\"')" >> "$GITHUB_OUTPUT"
echo "autobuilder=$(grep '^autobuilder:' cli/internal/globals/versions.yaml | awk '{print $2}' | tr -d '\"')" >> "$GITHUB_OUTPUT"
echo "rules=$(grep '^rules:' cli/internal/globals/versions.yaml | awk '{print $2}' | tr -d '\"')" >> "$GITHUB_OUTPUT"
if [ -n "${{ steps.versions.outputs.go_server }}" ]; then
echo "go_server=$(grep '^go-server:' cli/internal/globals/versions.yaml | awk '{print $2}' | tr -d '\"')" >> "$GITHUB_OUTPUT"
fi

- name: Check for changes
id: diff
Expand All @@ -103,6 +118,9 @@ jobs:
if [ "${{ steps.versions.outputs.rules }}" != "${{ steps.current.outputs.rules }}" ]; then
CHANGES="${CHANGES}- **rules**: \`${{ steps.current.outputs.rules }}\` → \`${{ steps.versions.outputs.rules }}\`"$'\n'
fi
if [ -n "${{ steps.versions.outputs.go_server }}" ] && [ "${{ steps.versions.outputs.go_server }}" != "${{ steps.current.outputs.go_server }}" ]; then
CHANGES="${CHANGES}- **go-server**: \`${{ steps.current.outputs.go_server }}\` → \`${{ steps.versions.outputs.go_server }}\`"$'\n'
fi

if [ -z "$CHANGES" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
Expand All @@ -124,6 +142,9 @@ jobs:
sed -i 's|^analyzer: .*|analyzer: "${{ steps.versions.outputs.analyzer }}"|' cli/internal/globals/versions.yaml
sed -i 's|^autobuilder: .*|autobuilder: "${{ steps.versions.outputs.autobuilder }}"|' cli/internal/globals/versions.yaml
sed -i 's|^rules: .*|rules: "${{ steps.versions.outputs.rules }}"|' cli/internal/globals/versions.yaml
if [ -n "${{ steps.versions.outputs.go_server }}" ]; then
sed -i 's|^go-server: .*|go-server: "${{ steps.versions.outputs.go_server }}"|' cli/internal/globals/versions.yaml
fi

echo "Updated versions.yaml:"
cat cli/internal/globals/versions.yaml
Expand Down
20 changes: 16 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MAKE ?= make
GRADLEW := $(CURDIR)/core/gradlew
INSTALL ?= install
APT_GET ?= apt-get

PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
Expand All @@ -13,19 +14,26 @@ CLI_BINARY_NAME := opentaint
CLI_DEV_BINARY_NAME := opentaint-dev
ANALYZER_TASK := :projectAnalyzerJar
AUTOBUILDER_TASK := opentaint-jvm-autobuilder:projectAutoBuilderJar
GO_SERVER_TASK := :opentaint-ir:go:buildGoServer

ANALYZER_JAR := $(CORE_DIR)/build/libs/opentaint-project-analyzer.jar
AUTOBUILDER_JAR := $(CORE_DIR)/opentaint-jvm-autobuilder/build/libs/opentaint-project-auto-builder.jar
GO_SERVER_BINARY := $(CORE_DIR)/opentaint-ir/go/go-ssa-server/go-ssa-server
RULES_SRC := rules/ruleset
INSTALLED_ANALYZER_JAR := $(LIBDIR)/$(notdir $(ANALYZER_JAR))
INSTALLED_AUTOBUILDER_JAR := $(LIBDIR)/$(notdir $(AUTOBUILDER_JAR))
INSTALLED_RULES_DIR := $(LIBDIR)/rules
INSTALLED_CLI_BINARY := $(BINDIR)/$(CLI_BINARY_NAME)
INSTALLED_DEV_BINARY := $(BINDIR)/$(CLI_DEV_BINARY_NAME)
INSTALLED_GO_SERVER := $(LIBDIR)/$(notdir $(GO_SERVER_BINARY))

.PHONY: all core projectAnalyzerJar core/autobuilder cli install clean
.PHONY: all core projectAnalyzerJar core/autobuilder go-server cli install install-deps clean

all: core cli
all: core go-server cli

install-deps:
$(APT_GET) update
$(APT_GET) install -y protobuf-compiler

core:
cd $(CORE_DIR) && $(GRADLEW) $(ANALYZER_TASK) $(AUTOBUILDER_TASK)
Expand All @@ -36,14 +44,18 @@ projectAnalyzerJar:
core/autobuilder:
cd $(CORE_DIR) && $(GRADLEW) $(AUTOBUILDER_TASK)

go-server:
cd $(CORE_DIR) && $(GRADLEW) $(GO_SERVER_TASK)

cli:
$(MAKE) -C $(CLI_DIR) build

install: core
install: core go-server
mkdir -p $(BINDIR) $(LIBDIR)
$(MAKE) -C $(CLI_DIR) install PREFIX=$(PREFIX) BINDIR=$(abspath $(BINDIR))
$(INSTALL) -m 0644 $(ANALYZER_JAR) $(INSTALLED_ANALYZER_JAR)
$(INSTALL) -m 0644 $(AUTOBUILDER_JAR) $(INSTALLED_AUTOBUILDER_JAR)
$(INSTALL) -m 0755 $(GO_SERVER_BINARY) $(INSTALLED_GO_SERVER)
rm -rf $(INSTALLED_RULES_DIR)
mkdir -p $(INSTALLED_RULES_DIR)
cp -R $(RULES_SRC)/. $(INSTALLED_RULES_DIR)/
Expand All @@ -54,7 +66,7 @@ install: core
'BIN_DIR=$$(CDPATH= cd -- "$$(dirname -- "$$SELF")" && pwd -P)' \
'PREFIX_DIR=$$(CDPATH= cd -- "$$BIN_DIR/.." && pwd)' \
'LIB_DIR="$$PREFIX_DIR/lib"' \
'exec "$$BIN_DIR/$(CLI_BINARY_NAME)" --experimental --analyzer-jar "$$LIB_DIR/$(notdir $(ANALYZER_JAR))" --autobuilder-jar "$$LIB_DIR/$(notdir $(AUTOBUILDER_JAR))" "$$@"' \
'exec "$$BIN_DIR/$(CLI_BINARY_NAME)" --experimental --analyzer-jar "$$LIB_DIR/$(notdir $(ANALYZER_JAR))" --autobuilder-jar "$$LIB_DIR/$(notdir $(AUTOBUILDER_JAR))" --go-server-binary "$$LIB_DIR/$(notdir $(GO_SERVER_BINARY))" "$$@"' \
> $(INSTALLED_DEV_BINARY)
chmod 0755 $(INSTALLED_DEV_BINARY)
$(INSTALLED_CLI_BINARY) pull
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/analyzer_inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func addDataflowApproximations(b *AnalyzerBuilder, paths []string, analyzerJarPa

absPaths := make([]string, 0, len(paths))
for _, approxPath := range paths {
absPaths = append(absPaths, log.AbsPathOrExit(approxPath, "dataflow-approximations"))
absPaths = append(absPaths, log.AbsPathOrExit(approxPath, "java-models"))
}

classDirs, err := approximation.ResolveAll(absPaths, newApproximationBuilder(analyzerJarPath))
Expand All @@ -28,6 +28,6 @@ func addDataflowApproximations(b *AnalyzerBuilder, paths []string, analyzerJarPa

func addPassthroughApproximations(b *AnalyzerBuilder, paths []string) {
for _, passthrough := range paths {
b.AddPassthroughApproximations(log.AbsPathOrExit(passthrough, "passthrough-approximations"))
b.AddPassthroughApproximations(log.AbsPathOrExit(passthrough, "passthrough-models"))
}
}
2 changes: 1 addition & 1 deletion cli/cmd/approximation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ methods the analyzer cannot see into, together with the exact dependency version
models are written against.

Compile one with 'opentaint compile approximations', and apply it with the
--dataflow-approximations flag of 'opentaint scan' or 'opentaint test approximation run'.`,
--java-models flag of 'opentaint scan' or 'opentaint test dataflow-model run'.`,
}

var approximationInitCmd = &cobra.Command{
Expand Down
38 changes: 25 additions & 13 deletions cli/cmd/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,33 @@ func currentCompileBuilder(projectPath string) *utils.OpentaintCommandBuilder {
// dockerCompileSuggestion builds the "try Docker-based compilation" fallback hint.
func dockerCompileSuggestion() output.Suggestion {
return output.Suggestion{
Description: dockerFallbackHintPrefix + "compilation:",
Description: "If the required Java is missing, set JAVA_HOME or compile in a container instead:",
Command: utils.BuildCompileCommandWithDocker(currentCompileBuilder(""), ProjectPath, OutputProjectModelPath),
}
}

// compileCmd represents the compile command
var compileCmd = &cobra.Command{
Use: "compile project",
Short: "Compile your Java or Kotlin project",
Use: "compile <project>",
Short: "Compile a project into a reusable project model",
Args: cobra.ExactArgs(1), // require exactly one argument
Long: `This command takes a required path to the project, automatically detects Java/Kotlin build system, modules and dependencies and compiles project model.
Long: `Compile a project into a project model that you can scan many times. OpenTaint finds the build system, collects the modules and dependencies, and builds the project.

Arguments:
project - Path to a project to compile (required)
`,
The project argument is the path to the project root. It is required. Use --output to set the project model directory. This directory must not exist before the command runs.

Later scans can use the model without a new build. This makes repeated scans fast.

To scan the model, use "opentaint scan --project-model".`,
Example: ` # Compile the current directory into a project model
opentaint compile . -o ./model

# Make sure the inputs are correct, without a build
opentaint compile . -o ./model --dry-run

# Recipe: compile one time, then scan with different settings
opentaint compile ./my-app -o ./model
opentaint scan --project-model ./model --ruleset ./team-rules -o team.sarif
opentaint scan --project-model ./model --severity error -o errors.sarif`,
Annotations: map[string]string{"PrintConfig": "true"},
Run: func(cmd *cobra.Command, args []string) {
ProjectPath = args[0]
Expand All @@ -72,33 +84,33 @@ Arguments:
sb.Line()
}
sb.FieldNode("Project", absProjectRoot).
FieldNode("Output project model", absOutputProjectModelPath).
FieldNode("Project model", absOutputProjectModelPath).
FieldNode("Autobuilder", utils.ArtifactVersionWithPath(globals.ArtifactByKind("autobuilder"))).
Render()

if DryRunCompile {
out.Blank()
failOnInvalidInputs(func() error { return validation.ValidateCompileInputs(absProjectRoot, absOutputProjectModelPath) })
runDryRun("Compilation")
runDryRun("compilation")
return
}

autobuilderJarPath, err := ensureAutobuilderAvailable()
if err != nil {
out.Fatalf("Native compile preparation failed: %s", err)
failf("Native compile preparation failed: %s", err)
}

compileJavaRunner := newAutobuilderJavaRunner()
if _, err := compileJavaRunner.EnsureJava(); err != nil {
out.Fatalf("Failed to resolve Java for compilation: %s", err)
failf("Failed to resolve Java for compilation: %s", err)
}

if err := out.RunWithSpinner("Compiling project model", func() error {
return compile(absProjectRoot, absOutputProjectModelPath, autobuilderJarPath, compileJavaRunner)
}); err == nil {
out.Blank()
printCompileSummary(absOutputProjectModelPath)
suggest("To scan project run", utils.BuildScanCommandFromCompile(projectRoot, absOutputProjectModelPath))
suggest("To scan the compiled project model, run:", utils.BuildScanCommandFromCompile(projectRoot, absOutputProjectModelPath))
} else {
out.InteractiveBlank()
failWith(1, fmt.Sprintf("Native compile has failed: %s", err), dockerCompileSuggestion())
Expand All @@ -109,7 +121,7 @@ Arguments:
func init() {
rootCmd.AddCommand(compileCmd)

compileCmd.Flags().StringVarP(&OutputProjectModelPath, "output", "o", "", `Path to the result project model`)
compileCmd.Flags().StringVarP(&OutputProjectModelPath, "output", "o", "", `Path to the project model directory to create (required, must not exist)`)
_ = compileCmd.MarkFlagRequired("output")
compileCmd.Flags().BoolVar(&DryRunCompile, "dry-run", false, "Validate inputs and show what would run without compiling")
compileCmd.Flags().StringVar(&CompileLogFile, "log-file", "", "Path to the log file (default: <cache-dir>/logs/<timestamp>.log)")
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/compile_approximations_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ needed to compile ahead of time or to see compilation errors on their own.`,
Field("Compiled models written to", approximation.ClassesDir(outputPath)).
Render()
suggest("To apply these models in a scan run",
fmt.Sprintf("opentaint scan <project-model> --dataflow-approximations %s", args[0]))
fmt.Sprintf("opentaint scan <project-model> --java-models %s", args[0]))
},
}

Expand Down
36 changes: 34 additions & 2 deletions cli/cmd/dry_run.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cmd

import "fmt"
import (
"os"
"strings"
)

func failOnInvalidInputs(validate func() error) {
if err := validate(); err != nil {
Expand All @@ -9,5 +12,34 @@ func failOnInvalidInputs(validate func() error) {
}

func runDryRun(skippedAction string) {
out.Print(fmt.Sprintf("Dry run mode. Inputs validated. %s skipped.", skippedAction))
out.Printf("Dry run complete. Inputs validated, %s skipped.", skippedAction)
suggest("To run for real, run:", rerunWithoutDryRun())
}

func rerunWithoutDryRun() string {
args := []string{"opentaint"}
for _, arg := range os.Args[1:] {
if arg == "--dry-run" || strings.HasPrefix(arg, "--dry-run=") {
continue
}
args = append(args, shellQuote(arg))
}
return strings.Join(args, " ")
}

func shellQuote(arg string) string {
if arg != "" && !strings.ContainsFunc(arg, shellUnsafe) {
return arg
}
return "'" + strings.ReplaceAll(arg, "'", `'\''`) + "'"
}

func shellUnsafe(r rune) bool {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
return false
case strings.ContainsRune("_@%+=:,./-", r):
return false
}
return true
}
28 changes: 28 additions & 0 deletions cli/cmd/exit_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"fmt"

"github.com/seqra/opentaint/internal/analyzer"
)

func analyzerExitCodeRows() string {
rows := ""
for _, code := range []int{analyzer.ExitException, analyzer.ExitOOM, analyzer.ExitTimeout, analyzer.ExitConfigError} {
rows += fmt.Sprintf("\n %-3d %s", code, analyzer.ExitMessage(code))
}
return rows
}

func scanExitCodesHelp(completedLine string) string {
return `Exit codes:
0 ` + completedLine + `
1 General failure (configuration or infrastructure error)` + analyzerExitCodeRows()
}

func testExitCodesHelp(passedLine string) string {
return `Exit codes:
0 ` + passedLine + `
1 General failure (configuration or infrastructure error)
2 One or more tests failed (false negatives, false positives, or skipped samples)` + analyzerExitCodeRows()
}
Loading
Loading