Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ All notable changes to CryptoScan are recorded here. This project follows
cask's `binary "cryptoscan"` stanza installs the binary. `goreleaser check`
now exits 0, and a snapshot build writes the same six archives as before:
tar.gz for Linux and macOS, zip for Windows.
- **A scan target URL is printed, reported and saved without its user
information.** The JSON report's `scanTarget`, the `scanTarget` read from a
`--baseline` file, the text and `pqc` reports, the line printed when no file
was scanned, and an error that quotes the target name a repository URL
without the user name and password in it. The clone uses the URL as it was
typed.

### Fixed

Expand Down
4 changes: 4 additions & 0 deletions internal/cli/baseline.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func loadBaseline(path string) (*baseline, error) {
if err := json.Unmarshal(data, &b); err != nil {
return nil, err
}
// A report from an earlier release holds the target as it was typed,
// which for a repository URL can include a user name and password. The
// baseline keeps and compares the target without them.
b.ScanTarget = displayTarget(b.ScanTarget)
return &b, nil
}

Expand Down
45 changes: 45 additions & 0 deletions internal/cli/baseline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,48 @@ func TestBaselineCoversURLScanWithRelativePaths(t *testing.T) {
})
}
}

// A baseline saved by an earlier release holds the scan target as it was
// typed, which for a repository URL can include a user name and a password.
// The baseline is read without them. A target with no user information, such
// as an absolute path holding "@", is read as it is and is still the root of
// the baseline's file paths.
func TestBaselineScanTargetIsReadWithoutUserInformation(t *testing.T) {
target, user, password := userinfoTarget()
local := filepath.Join(t.TempDir(), "build@2")

tests := []struct {
name string
saved string
want string
}{
{"url with user information", target, shownUserinfoTarget},
{"absolute path holding @", local, local},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := json.Marshal(map[string]any{"scanTarget": tt.saved, "findings": []scanner.Finding{}})
if err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "baseline.json")
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatal(err)
}

b, err := loadBaseline(path)
if err != nil {
t.Fatal(err)
}
if b.ScanTarget != tt.want {
t.Errorf("scanTarget = %q, want %q", b.ScanTarget, tt.want)
}
assertHoldsNone(t, "the baseline's scanTarget", b.ScanTarget, user, password)
if filepath.IsAbs(tt.want) {
if root := b.root(nil); root != tt.want {
t.Errorf("root = %q, want %q", root, tt.want)
}
}
})
}
}
35 changes: 31 additions & 4 deletions internal/cli/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,11 @@ func scanPipeline(cmd *cobra.Command, target string, opts pipelineOptions) (*sca
return nil, "", err
}

// A repository URL can carry a user name and password for the clone. The
// scanner is given the target as it was typed; every copy the program
// prints or saves is shownTarget, which leaves them out.
shownTarget := displayTarget(target)

// Create scanner config
cfg := scanner.Config{
Target: target,
Expand Down Expand Up @@ -421,13 +426,17 @@ func scanPipeline(cmd *cobra.Command, target string, opts pipelineOptions) (*sca
}
}

// Resolve target path
// Resolve target path. shownPath is cfg.Target as the program prints it.
shownPath := shownTarget
if !isURL(target) {
absPath, err := filepath.Abs(target)
if err != nil {
return nil, "", fmt.Errorf("invalid path: %w", err)
}
cfg.Target = absPath
if shownPath, err = filepath.Abs(shownTarget); err != nil {
return nil, "", fmt.Errorf("invalid path: %w", err)
}
}

// Print banner
Expand All @@ -445,7 +454,14 @@ func scanPipeline(cmd *cobra.Command, target string, opts pipelineOptions) (*sca
s := scanner.New(cfg)
results, err := s.Scan()
if err != nil {
return nil, "", withHint(fmt.Errorf("scan failed: %w", err), scanErrorHint(err))
err = fmt.Errorf("scan failed: %w", err)
if shownPath != cfg.Target {
// A URL with a scheme the scanner does not clone, such as
// ssh://, is read as a local path, and the error for that path
// quotes it.
err = &shownError{err: err, message: strings.ReplaceAll(err.Error(), cfg.Target, shownPath)}
}
return nil, "", withHint(err, scanErrorHint(err))
}
duration := time.Since(startTime)

Expand Down Expand Up @@ -484,7 +500,7 @@ func scanPipeline(cmd *cobra.Command, target string, opts pipelineOptions) (*sca
unique: uniqueCount,
reported: len(results.Findings),
files: results.FilesScanned,
target: target,
target: shownTarget,
}, duration, !noColor)
}

Expand Down Expand Up @@ -513,7 +529,7 @@ func scanPipeline(cmd *cobra.Command, target string, opts pipelineOptions) (*sca

// Add metadata
results.ScanDuration = duration
results.ScanTarget = target
results.ScanTarget = shownTarget
results.ScanTime = startTime

// Generate report
Expand Down Expand Up @@ -715,6 +731,17 @@ func scanErrorHint(err error) string {
return ""
}

// shownError is a scan error whose message quotes the target as the program
// prints it. Unwrap returns the error underneath, so scanErrorHint chooses the
// next step for it the same way.
type shownError struct {
err error
message string
}

func (e *shownError) Error() string { return e.message }
func (e *shownError) Unwrap() error { return e.err }

// autoConfigSource says where an auto-detected config file was found relative
// to the scan target. The search walks up from the target, so a file in any
// directory above it applies to the scan without being part of what is
Expand Down
Loading
Loading