Skip to content

[REVIEW] CODERABBIT REVIEW - #1

Open
Kawixh wants to merge 11 commits into
review-basefrom
main
Open

[REVIEW] CODERABBIT REVIEW#1
Kawixh wants to merge 11 commits into
review-basefrom
main

Conversation

@Kawixh

@Kawixh Kawixh commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added automated release publishing to GitHub and Homebrew via GitHub Actions
    • Implemented scan snapshot caching and persistence for historical comparison
    • Added diff functionality to compare snapshots and detect project changes
    • Introduced rules testing capability for policy validation
    • Added JavaScript obfuscation detection with payload analysis
    • Integrated threat intelligence from OSV and npm registry
    • Implemented allowlist and blocklist policy controls for finding suppression
    • Added multiple output formats (JSON, table, plain text) for all reports
  • Documentation

    • Added Homebrew distribution guide and setup instructions
    • Added milestone documentation for release and update-check features

Kawixh added 10 commits June 17, 2026 16:38
Implement the milestone 6 global cache foundation with cache layout creation,
content-addressed built-in rule caching, source metadata, cache update/clean
commands, JSON reports, and offline scan cache preparation.

Also complete maintainer matching for local rules by extracting maintainer-style
metadata from package manifests and wiring it into rule and blocklist findings.
Implement milestone 07 threat source plumbing with OSV, npm registry metadata,
cached OpenSSF malicious package records, cached GitHub Advisory records, offline
cache reads, source status reporting, and required-source failure handling.

Wire threat findings into scan snapshots and reports while preserving local
policy findings from the previous rules milestone.
Add a bounded JavaScript analyzer for encoded payload recovery, sink-flow
detection, decoded payload caching, and scan finding integration. Extend finding
evidence with decoded payload metadata and cover analyzer behavior with unit and
scan integration tests.
Document the Malox Homebrew tap publishing flow, install and upgrade commands,
tap token setup, and first-release troubleshooting. Add a milestone for
non-blocking latest-release checks that preserve scan execution and JSON output.
@Kawixh Kawixh self-assigned this Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aa06abfd-8088-4c3e-91fd-2e7d52531264

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch main

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (10)
AGENTS.md (1)

7-7: ⚡ Quick win

Consider referencing specific verification tools for clarity.

Line 7 instructs to "run go build, test and code verification, format commands," but docs/go-code-guidelines.md (lines 944-954) specifies the exact tools that should be run: gofmt, goimports, go vet, golangci-lint, go test, go test -race, and govulncheck. Adding a reference to that section would provide clearer guidance and ensure consistency.

📝 Proposed refinement for line 7
-always run go build, test and code verification, format commands to ensure that the code is free of syntax errors, follows the Go code style guidelines, and passes all tests before finalizing the implementation. This will help maintain code quality and ensure that the implementation is robust and reliable.
+always run go build, test and code verification (gofmt, goimports, go vet, golangci-lint, go test, go test -race, govulncheck), and format commands as specified in docs/go-code-guidelines.md to ensure that the code is free of syntax errors, follows the Go code style guidelines, and passes all tests before finalizing the implementation. This will help maintain code quality and ensure that the implementation is robust and reliable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 7, The instruction on line 7 of the AGENTS.md file
references generic "go build, test and code verification, format commands"
without specifying the exact verification tools to use. Update this instruction
to reference the specific tools documented in the go-code-guidelines section
(gofmt, goimports, go vet, golangci-lint, go test, go test -race, and
govulncheck) to provide clearer and more consistent guidance. Add an explicit
reference to the go-code-guidelines documentation where these tools are detailed
to help developers know exactly which commands to execute.

Source: Coding guidelines

.github/workflows/release.yml (1)

20-23: ⚡ Quick win

Consider security hardening for Actions checkout.

Setting persist-credentials: false prevents the workflow from persisting GitHub credentials to disk, reducing risk if artifacts are compromised.

🔒 Suggested hardening
       - name: Checkout
         uses: actions/checkout@v6
         with:
           fetch-depth: 0
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 20 - 23, The actions/checkout@v6
step is missing the persist-credentials security hardening setting. Add
persist-credentials: false to the with section of the checkout action to prevent
GitHub credentials from being persisted to disk, which reduces the risk of
credential exposure if workflow artifacts are compromised.

Source: Linters/SAST tools

internal/node/deno.go (2)

107-120: ⚡ Quick win

Simplify the redundant prefix check.

The static analysis tool correctly identified that lines 112-114 contain a redundant check. The strings.TrimPrefix function already handles the case where the prefix is absent (it's a no-op), so the conditional is unnecessary.

♻️ Proposed simplification
 	if value == "" {
 		return "", ""
 	}
-	if strings.HasPrefix(value, "npm:") {
-		value = strings.TrimPrefix(value, "npm:")
-	}
+	value = strings.TrimPrefix(value, "npm:")
 	idx := strings.LastIndex(value, "@")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/node/deno.go` around lines 107 - 120, The parseDenoPackageKey
function contains a redundant conditional check before calling
strings.TrimPrefix. The strings.TrimPrefix function already handles the case
where the prefix is not present (it returns the original string unchanged), so
the if statement checking strings.HasPrefix is unnecessary. Remove the
conditional on lines that check for "npm:" prefix and simply call
strings.TrimPrefix directly without the condition, keeping the same logic but
eliminating the redundant check.

Source: Linters/SAST tools


115-119: Add test coverage for scoped package parsing.

The logic correctly handles scoped npm packages like @scope/pkg@1.0.0 by finding the last @ character (the version delimiter, not the scope's @). For @scope/pkg without a version, idx <= 0 returns the package name as-is with an empty version. However, the test suite only covers non-scoped packages (left-pad@1.3.0). Adding explicit test cases for scoped packages would strengthen confidence in this behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/node/deno.go` around lines 115 - 119, Add test cases to verify that
scoped npm packages are correctly parsed by the package name and version
splitting logic. The current tests only cover non-scoped packages like
"left-pad@1.3.0", but the code correctly handles scoped packages using LastIndex
to find the last "@" character (the version delimiter). Add test cases for:
scoped packages with versions like "`@scope/pkg`@1.0.0" (should split into
"`@scope/pkg`" and "1.0.0"), and scoped packages without versions like
"`@scope/pkg`" (should return "`@scope/pkg`" with empty version). These tests will
validate that the idx <= 0 check and bounds checking correctly handle the scope
prefix without incorrectly treating the scope's "@" as a version delimiter.
internal/node/bun.go (1)

21-30: ⚡ Quick win

Consider adding context cancellation support for lockfile parsing.

The parseBunLock function performs I/O-like operations (JSON parsing, iteration over potentially large maps) but doesn't accept a context.Context parameter. For large lockfiles, this could lead to uninterruptible operations. Consider accepting a context and checking ctx.Err() at loop boundaries.

Based on learnings, always pursue the goal and play devil's advocate: if a malicious or extremely large lockfile is encountered, the scanner should be able to cancel gracefully.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/node/bun.go` around lines 21 - 30, The parseBunLock function lacks
context cancellation support which is necessary for handling potentially large
or malicious lockfiles that could cause uninterruptible operations. Modify the
parseBunLock function signature to accept a context.Context as the first
parameter, then add context cancellation checks by calling ctx.Err() at
appropriate points throughout the function, particularly at loop boundaries
where iteration over potentially large maps occurs, to ensure the operation can
be gracefully cancelled when needed.

Source: Learnings

internal/node/inventory.go (1)

341-357: 💤 Low value

Potential redundant sort in deduplication.

dedupeDependencies calls slices.SortFunc on line 345, but Build() also calls sortInventory() on line 113, which sorts dependencies again on line 296. Since dedupeDependencies is called before sortInventory (line 115 before line 113), the second sort is necessary. However, if the deduped output is already sorted, you could document this invariant or avoid the extra work.

💡 Alternative: Document the sorting contract

If dedupeDependencies guarantees sorted output, add a comment:

 func dedupeDependencies(deps []Dependency) []Dependency {
+	// Returns dependencies in sorted order after deduplication.
 	if len(deps) == 0 {

Then potentially skip re-sorting in sortInventory when that invariant is known.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/node/inventory.go` around lines 341 - 357, The dedupeDependencies
function sorts dependencies using slices.SortFunc before deduplicating them, and
this sorted output is then passed to sortInventory which sorts again, causing
redundant work. Add a comment to the dedupeDependencies function documenting
that it guarantees sorted output as a postcondition, so the contract is clear
that the returned slice is always sorted by the compareDependency ordering. This
makes the invariant explicit and allows for potential optimization of the
redundant sort in sortInventory.
internal/scan/types.go (1)

29-46: ⚡ Quick win

Consider adding JSON struct tags to serialized types.

The Snapshot struct and several others (File, SkipReason, SkippedFile, SkippedDirectory, Issue, Summary) are part of the scan snapshot schema that will be serialized to JSON (as evidenced by SchemaVersion = "malox.scan.snapshot.v1" and context showing this being written). Only ThreatSourceStatus has JSON tags. Without explicit tags, field names will serialize using Go's default casing, which may not match your intended schema.

📋 Example: Add JSON tags for consistent serialization
 type Snapshot struct {
-	SchemaVersion      string
-	ScannerVersion     string
-	ScanID             string
+	SchemaVersion      string                  `json:"schema_version"`
+	ScannerVersion     string                  `json:"scanner_version"`
+	ScanID             string                  `json:"scan_id"`
	// ... continue for all fields

Apply similar tags to File, SkipReason, SkippedFile, SkippedDirectory, Issue, and Summary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/scan/types.go` around lines 29 - 46, The Snapshot struct and related
structs (File, SkipReason, SkippedFile, SkippedDirectory, Issue, and Summary)
are serialized to JSON but lack explicit JSON struct tags on their fields. Add
json struct tags to all exported fields in these structs, using the
ThreatSourceStatus struct as a reference for the tagging pattern already
implemented. Apply consistent JSON tag naming (typically snake_case) across all
fields in Snapshot and the other mentioned structs to ensure the serialized JSON
output matches the intended malox.scan.snapshot.v1 schema format.
internal/threat/osv.go (1)

199-215: 💤 Low value

Consider adding overflow protection in version parsing.

The manual digit parsing at lines 206-210 could theoretically overflow int for very long digit sequences (e.g., maliciously crafted version strings with hundreds of digits). While OSV data is curated and this is unlikely in practice, using strconv.Atoi or capping the iteration count would be more defensive.

♻️ Optional refactor using strconv
 func versionParts(version string) []int {
 	version = strings.TrimPrefix(version, "v")
 	version, _, _ = strings.Cut(version, "-")
 	chunks := strings.Split(version, ".")
 	out := make([]int, 0, len(chunks))
 	for _, chunk := range chunks {
-		var value int
-		for _, r := range chunk {
-			if r < '0' || r > '9' {
-				break
-			}
-			value = value*10 + int(r-'0')
-		}
+		// Extract leading digits
+		var i int
+		for i = 0; i < len(chunk); i++ {
+			if chunk[i] < '0' || chunk[i] > '9' {
+				break
+			}
+		}
+		digits := chunk[:i]
+		value, _ := strconv.Atoi(digits) // Ignore error, default to 0
 		out = append(out, value)
 	}
 	return out
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/threat/osv.go` around lines 199 - 215, The versionParts function
manually parses digits into an int value using a loop that multiplies by 10 and
adds each digit without overflow protection, which could overflow for
maliciously crafted version strings with very long digit sequences. Replace the
manual digit parsing loop (the for loop iterating over rune r in chunk) with
strconv.Atoi to safely convert the chunk string to an integer, or alternatively
add a cap on the iteration count to prevent excessive accumulation of the value
variable.
internal/threat/threat.go (2)

368-375: 💤 Low value

Unchecked error from deferred resp.Body.Close().

The deferred resp.Body.Close() at line 369 does not check the returned error. While Close errors on HTTP response bodies are often safe to ignore (the response has been read), explicitly acknowledging the ignored error improves code clarity.

♻️ Proposed fix to explicitly ignore Close error
 func readHTTPResponse(resp *http.Response) ([]byte, error) {
-	defer resp.Body.Close()
+	defer func() { _ = resp.Body.Close() }()
 	data, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))
 	if err != nil {
 		return nil, err
 	}
 	return data, nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/threat/threat.go` around lines 368 - 375, In the readHTTPResponse
function, the deferred resp.Body.Close() call ignores the returned error without
explicitly acknowledging it. Replace the simple defer statement with a deferred
anonymous function that captures and explicitly ignores the error returned by
Close(), using the blank identifier pattern such as defer func() { _ =
resp.Body.Close() }() to make it clear that the error is intentionally ignored.

Source: Linters/SAST tools


344-366: ⚖️ Poor tradeoff

Limited retry logic only covers 5xx errors.

The retry loop at lines 345-366 uses for attempt := range 2 (attempts 0 and 1, so one retry). However, it only retries on 5xx server errors (line 356-359) and not on transient network errors (connection failures, timeouts). Network errors at line 347 are captured but break immediately without retry.

Consider whether the current retry strategy aligns with the resilience goals for threat-intelligence queries. If transient network failures should be retried, the logic would need to distinguish retryable errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/threat/threat.go` around lines 344 - 366, The retry logic in the
loop starting with `for attempt := range 2` does not properly distinguish
between transient and permanent errors. Currently, network errors from
`opts.httpClient().Do(req)` and readHTTPResponse() are caught and retried, but
the strategy should be more selective—only truly transient errors like timeouts
or connection resets should trigger a retry. Review the error handling at the
err assignment and readErr assignment points, and add type assertions or error
checking to identify transient network errors using appropriate error checking
methods, while allowing permanent errors to fail immediately without retry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 5-7: The release workflow is missing test execution before
publishing releases. Add a new step in the .github/workflows/release.yml
workflow that runs `go test` to validate code quality. Position this test step
after the "Set up Go" step and before the "Select next version" step to ensure
tests pass before any release builds or publications occur.

In @.goreleaser.yml:
- Around line 47-63: The brews configuration in the goreleaser.yml file has
inconsistent repository ownership that conflicts with documentation references.
Change the owner field in the brews section from darckorp to Kawixh, and update
the corresponding homepage URL from https://github.com/darckorp/malox to
https://github.com/Kawixh/malox to ensure the Homebrew formula is published to
the correct repository and displays the correct project homepage.
- Around line 5-7: The before hooks section in .goreleaser.yml currently only
contains the `go mod tidy` hook but does not execute tests as required by the
coding guidelines. Add a test execution hook (such as `go test ./...`) as an
additional entry in the before hooks list under the hooks key to ensure tests
are run before the release build is created.

In `@internal/cache/cache.go`:
- Line 619: The parseTime function call on the fetchedAt assignment is silently
discarding the error return value using the blank identifier, which means
malformed timestamp data in source.FetchedAt will result in a zero time value
with no indication of failure. Modify the code to capture both return values
from parseTime, check if an error was returned, and either log the error, return
an error from the containing function, or otherwise handle the parse failure
appropriately to prevent silent data corruption.
- Around line 97-103: The snapshot verification in the LoadSnapshot method only
checks that the ScanID matches after reloading, but does not verify the
integrity of the entire snapshot structure. To strengthen the verification and
detect potential corruption from JSON marshaling/unmarshaling issues, add a
comprehensive comparison beyond the ScanID check. This could include comparing
the full snapshot object byte-level (by re-marshaling to JSON and comparing) or
computing a checksum of the entire snapshot before writing and comparing it
after loading. Replace or supplement the current ScanID-only comparison with a
full snapshot integrity check to catch silent corruption in other fields like
timestamps or nested structures.

In `@internal/node/jsanalysis/jsanalysis.go`:
- Around line 916-923: The boundary check before reading 2 hex digits in the hex
escape handling logic contains an off-by-one error. The condition `i+1 <=
len(source)` only validates that position `i+1` is within bounds, but reading at
positions `i` and `i+1` requires ensuring both indices are valid. Change the
condition from `i+1 <= len(source)` to `i+2 <= len(source)` to properly validate
that there are 2 characters available to read starting from position `i`.

In `@internal/threat/threat.go`:
- Around line 335-342: The `ctx` parameter in the `do` function is shadowed
before being used, as a new timeout context is created from `req.Context()`
instead of the passed `ctx`. Fix this by modifying the context.WithTimeout call
to use the passed `ctx` parameter as the parent context instead of
`req.Context()`. This ensures the passed context parameter is properly respected
as the base context for creating the timeout context, which may carry important
deadlines or values from the caller.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 20-23: The actions/checkout@v6 step is missing the
persist-credentials security hardening setting. Add persist-credentials: false
to the with section of the checkout action to prevent GitHub credentials from
being persisted to disk, which reduces the risk of credential exposure if
workflow artifacts are compromised.

In `@AGENTS.md`:
- Line 7: The instruction on line 7 of the AGENTS.md file references generic "go
build, test and code verification, format commands" without specifying the exact
verification tools to use. Update this instruction to reference the specific
tools documented in the go-code-guidelines section (gofmt, goimports, go vet,
golangci-lint, go test, go test -race, and govulncheck) to provide clearer and
more consistent guidance. Add an explicit reference to the go-code-guidelines
documentation where these tools are detailed to help developers know exactly
which commands to execute.

In `@internal/node/bun.go`:
- Around line 21-30: The parseBunLock function lacks context cancellation
support which is necessary for handling potentially large or malicious lockfiles
that could cause uninterruptible operations. Modify the parseBunLock function
signature to accept a context.Context as the first parameter, then add context
cancellation checks by calling ctx.Err() at appropriate points throughout the
function, particularly at loop boundaries where iteration over potentially large
maps occurs, to ensure the operation can be gracefully cancelled when needed.

In `@internal/node/deno.go`:
- Around line 107-120: The parseDenoPackageKey function contains a redundant
conditional check before calling strings.TrimPrefix. The strings.TrimPrefix
function already handles the case where the prefix is not present (it returns
the original string unchanged), so the if statement checking strings.HasPrefix
is unnecessary. Remove the conditional on lines that check for "npm:" prefix and
simply call strings.TrimPrefix directly without the condition, keeping the same
logic but eliminating the redundant check.
- Around line 115-119: Add test cases to verify that scoped npm packages are
correctly parsed by the package name and version splitting logic. The current
tests only cover non-scoped packages like "left-pad@1.3.0", but the code
correctly handles scoped packages using LastIndex to find the last "@" character
(the version delimiter). Add test cases for: scoped packages with versions like
"`@scope/pkg`@1.0.0" (should split into "`@scope/pkg`" and "1.0.0"), and scoped
packages without versions like "`@scope/pkg`" (should return "`@scope/pkg`" with
empty version). These tests will validate that the idx <= 0 check and bounds
checking correctly handle the scope prefix without incorrectly treating the
scope's "@" as a version delimiter.

In `@internal/node/inventory.go`:
- Around line 341-357: The dedupeDependencies function sorts dependencies using
slices.SortFunc before deduplicating them, and this sorted output is then passed
to sortInventory which sorts again, causing redundant work. Add a comment to the
dedupeDependencies function documenting that it guarantees sorted output as a
postcondition, so the contract is clear that the returned slice is always sorted
by the compareDependency ordering. This makes the invariant explicit and allows
for potential optimization of the redundant sort in sortInventory.

In `@internal/scan/types.go`:
- Around line 29-46: The Snapshot struct and related structs (File, SkipReason,
SkippedFile, SkippedDirectory, Issue, and Summary) are serialized to JSON but
lack explicit JSON struct tags on their fields. Add json struct tags to all
exported fields in these structs, using the ThreatSourceStatus struct as a
reference for the tagging pattern already implemented. Apply consistent JSON tag
naming (typically snake_case) across all fields in Snapshot and the other
mentioned structs to ensure the serialized JSON output matches the intended
malox.scan.snapshot.v1 schema format.

In `@internal/threat/osv.go`:
- Around line 199-215: The versionParts function manually parses digits into an
int value using a loop that multiplies by 10 and adds each digit without
overflow protection, which could overflow for maliciously crafted version
strings with very long digit sequences. Replace the manual digit parsing loop
(the for loop iterating over rune r in chunk) with strconv.Atoi to safely
convert the chunk string to an integer, or alternatively add a cap on the
iteration count to prevent excessive accumulation of the value variable.

In `@internal/threat/threat.go`:
- Around line 368-375: In the readHTTPResponse function, the deferred
resp.Body.Close() call ignores the returned error without explicitly
acknowledging it. Replace the simple defer statement with a deferred anonymous
function that captures and explicitly ignores the error returned by Close(),
using the blank identifier pattern such as defer func() { _ = resp.Body.Close()
}() to make it clear that the error is intentionally ignored.
- Around line 344-366: The retry logic in the loop starting with `for attempt :=
range 2` does not properly distinguish between transient and permanent errors.
Currently, network errors from `opts.httpClient().Do(req)` and
readHTTPResponse() are caught and retried, but the strategy should be more
selective—only truly transient errors like timeouts or connection resets should
trigger a retry. Review the error handling at the err assignment and readErr
assignment points, and add type assertions or error checking to identify
transient network errors using appropriate error checking methods, while
allowing permanent errors to fail immediately without retry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 37f290fc-1e08-4f19-b36f-2f1a5f6dee2b

📥 Commits

Reviewing files that changed from the base of the PR and between 899e643 and c864a08.

⛔ Files ignored due to path filters (7)
  • go.sum is excluded by !**/*.sum
  • testdata/node/bun/bun.lock is excluded by !**/*.lock
  • testdata/node/deno/deno.lock is excluded by !**/*.lock
  • testdata/node/npm/node_modules/left-pad/package.json is excluded by !**/node_modules/**
  • testdata/node/npm/package-lock.json is excluded by !**/package-lock.json
  • testdata/node/pnpm/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • testdata/node/yarn/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (70)
  • .github/workflows/release.yml
  • .gitignore
  • .goreleaser.yml
  • .release-bump.example
  • AGENTS.md
  • bin/malox
  • docs/homebrew-distribution.md
  • docs/milestones/11-homebrew-distribution-and-update-checks.md
  • docs/milestones/README.md
  • go.mod
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/app/help.go
  • internal/app/parse.go
  • internal/cache/cache.go
  • internal/cache/cache_test.go
  • internal/cache/global.go
  • internal/cache/global_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/diff/diff.go
  • internal/diff/diff_test.go
  • internal/fileid/fileid.go
  • internal/fileid/fileid_test.go
  • internal/node/bun.go
  • internal/node/deno.go
  • internal/node/inventory.go
  • internal/node/inventory_test.go
  • internal/node/jsanalysis/jsanalysis.go
  • internal/node/jsanalysis/jsanalysis_test.go
  • internal/node/manifest.go
  • internal/node/npm.go
  • internal/node/owner.go
  • internal/node/pnpm.go
  • internal/node/purl.go
  • internal/node/purl_test.go
  • internal/node/types.go
  • internal/node/yaml.go
  • internal/node/yarn.go
  • internal/report/cache.go
  • internal/report/diff.go
  • internal/report/diff_test.go
  • internal/report/rules.go
  • internal/report/scan.go
  • internal/report/scan_test.go
  • internal/rules/defaults/allowlist-template.json
  • internal/rules/defaults/blocklist-template.json
  • internal/rules/defaults/builtin-rules.json
  • internal/rules/evaluate.go
  • internal/rules/load.go
  • internal/rules/match.go
  • internal/rules/rules_test.go
  • internal/rules/semver.go
  • internal/rules/test.go
  • internal/rules/types.go
  • internal/scan/scan.go
  • internal/scan/scan_test.go
  • internal/scan/types.go
  • internal/threat/npm.go
  • internal/threat/osv.go
  • internal/threat/threat.go
  • internal/threat/threat_test.go
  • testdata/node/bun/package.json
  • testdata/node/deno/deno.json
  • testdata/node/npm/.malox/indexes/files.jsonl
  • testdata/node/npm/.malox/latest.json
  • testdata/node/npm/.malox/scans/2026-06-17T12-44-05.188532000Z.json
  • testdata/node/npm/package.json
  • testdata/node/pnpm/package.json
  • testdata/node/yarn/package.json

Comment on lines +5 to +7
branches:
- main

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add test execution before releasing.

The workflow builds and publishes releases but does not run tests. As per coding guidelines, go test should run before release to ensure code quality.

🧪 Suggested test step

Add a test step after "Set up Go" and before "Select next version":

       - name: Set up Go
         uses: actions/setup-go@v6
         with:
           go-version-file: go.mod
           cache: true
 
+      - name: Run tests
+        run: go test -v ./...
+
       - name: Select next version
         id: version
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 5 - 7, The release workflow is
missing test execution before publishing releases. Add a new step in the
.github/workflows/release.yml workflow that runs `go test` to validate code
quality. Position this test step after the "Set up Go" step and before the
"Select next version" step to ensure tests pass before any release builds or
publications occur.

Source: Coding guidelines

Comment thread .goreleaser.yml
Comment on lines +5 to +7
before:
hooks:
- go mod tidy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Consider adding test execution to before hooks.

The before hooks run go mod tidy but do not execute tests. As per coding guidelines, tests should run before building releases.

🧪 Suggested test hook
 before:
   hooks:
     - go mod tidy
+    - go test ./...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.goreleaser.yml around lines 5 - 7, The before hooks section in
.goreleaser.yml currently only contains the `go mod tidy` hook but does not
execute tests as required by the coding guidelines. Add a test execution hook
(such as `go test ./...`) as an additional entry in the before hooks list under
the hooks key to ensure tests are run before the release build is created.

Source: Coding guidelines

Comment thread .goreleaser.yml
Comment on lines +47 to +63
brews:
- name: malox
ids:
- malox
repository:
owner: darckorp
name: homebrew-tap
branch: main
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Formula
homepage: "https://github.com/darckorp/malox"
description: "Fast cross-platform terminal security scanner for open source projects"
license: "MIT"
install: |
bin.install "malox"
test: |
system "#{bin}/malox", "--version"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Repository owner and homepage mismatch.

The Homebrew configuration uses owner: darckorp and homepage: "https://github.com/darckorp/malox", but all documentation consistently references Kawixh:

  • Line 19 in homebrew-distribution.md: Kawixh/homebrew-tap
  • Line 86 in homebrew-distribution.md: Kawixh/malox
  • Line 11 in milestone doc: Kawixh/homebrew-tap

This mismatch will cause the formula to be published to the wrong repository and the homepage link to be incorrect.

🔧 Proposed fix
 brews:
   - name: malox
     ids:
       - malox
     repository:
-      owner: darckorp
+      owner: Kawixh
       name: homebrew-tap
       branch: main
       token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
     directory: Formula
-    homepage: "https://github.com/darckorp/malox"
+    homepage: "https://github.com/Kawixh/malox"
     description: "Fast cross-platform terminal security scanner for open source projects"
     license: "MIT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
brews:
- name: malox
ids:
- malox
repository:
owner: darckorp
name: homebrew-tap
branch: main
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Formula
homepage: "https://github.com/darckorp/malox"
description: "Fast cross-platform terminal security scanner for open source projects"
license: "MIT"
install: |
bin.install "malox"
test: |
system "#{bin}/malox", "--version"
brews:
- name: malox
ids:
- malox
repository:
owner: Kawixh
name: homebrew-tap
branch: main
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Formula
homepage: "https://github.com/Kawixh/malox"
description: "Fast cross-platform terminal security scanner for open source projects"
license: "MIT"
install: |
bin.install "malox"
test: |
system "#{bin}/malox", "--version"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.goreleaser.yml around lines 47 - 63, The brews configuration in the
goreleaser.yml file has inconsistent repository ownership that conflicts with
documentation references. Change the owner field in the brews section from
darckorp to Kawixh, and update the corresponding homepage URL from
https://github.com/darckorp/malox to https://github.com/Kawixh/malox to ensure
the Homebrew formula is published to the correct repository and displays the
correct project homepage.

Comment thread internal/cache/cache.go
Comment on lines +97 to +103
verified, err := s.LoadSnapshot(ctx, snapshot.ScanID)
if err != nil {
return fmt.Errorf("verify scan snapshot: %w", err)
}
if verified.ScanID != snapshot.ScanID {
return fmt.Errorf("verify scan snapshot: got scan id %q, want %q", verified.ScanID, snapshot.ScanID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Verification only checks ScanID match, not full snapshot integrity.

The write verification reloads the snapshot and confirms only that ScanID matches. If JSON marshaling/unmarshaling introduced silent corruption in other fields (e.g., truncated timestamps, dropped nested structures), this check would not detect it.

🛡️ Proposed enhancement to strengthen verification

Consider adding a checksum or byte-level comparison:

 	verified, err := s.LoadSnapshot(ctx, snapshot.ScanID)
 	if err != nil {
 		return fmt.Errorf("verify scan snapshot: %w", err)
 	}
-	if verified.ScanID != snapshot.ScanID {
-		return fmt.Errorf("verify scan snapshot: got scan id %q, want %q", verified.ScanID, snapshot.ScanID)
+	// Verify by comparing the serialized form to catch any marshaling issues
+	verifiedData, err := marshalSnapshot(verified)
+	if err != nil {
+		return fmt.Errorf("verify scan snapshot: %w", err)
+	}
+	if !bytes.Equal(data, verifiedData) {
+		return fmt.Errorf("verify scan snapshot: marshaled data mismatch")
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
verified, err := s.LoadSnapshot(ctx, snapshot.ScanID)
if err != nil {
return fmt.Errorf("verify scan snapshot: %w", err)
}
if verified.ScanID != snapshot.ScanID {
return fmt.Errorf("verify scan snapshot: got scan id %q, want %q", verified.ScanID, snapshot.ScanID)
}
verified, err := s.LoadSnapshot(ctx, snapshot.ScanID)
if err != nil {
return fmt.Errorf("verify scan snapshot: %w", err)
}
// Verify by comparing the serialized form to catch any marshaling issues
verifiedData, err := marshalSnapshot(verified)
if err != nil {
return fmt.Errorf("verify scan snapshot: %w", err)
}
if !bytes.Equal(data, verifiedData) {
return fmt.Errorf("verify scan snapshot: marshaled data mismatch")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cache/cache.go` around lines 97 - 103, The snapshot verification in
the LoadSnapshot method only checks that the ScanID matches after reloading, but
does not verify the integrity of the entire snapshot structure. To strengthen
the verification and detect potential corruption from JSON
marshaling/unmarshaling issues, add a comprehensive comparison beyond the ScanID
check. This could include comparing the full snapshot object byte-level (by
re-marshaling to JSON and comparing) or computing a checksum of the entire
snapshot before writing and comparing it after loading. Replace or supplement
the current ScanID-only comparison with a full snapshot integrity check to catch
silent corruption in other fields like timestamps or nested structures.

Comment thread internal/cache/cache.go
}
out := make([]scan.ThreatSourceStatus, 0, len(d.ThreatSources))
for _, source := range d.ThreatSources {
fetchedAt, _ := parseTime(source.FetchedAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Silent error suppression when parsing FetchedAt timestamp.

Line 619 uses fetchedAt, _ := parseTime(source.FetchedAt) which silently ignores parse errors. If source.FetchedAt contains malformed timestamp data, this will produce a zero time without any warning or validation failure.

🛡️ Proposed fix to log or handle parse errors
 	for _, source := range d.ThreatSources {
-		fetchedAt, _ := parseTime(source.FetchedAt)
+		fetchedAt, err := parseTime(source.FetchedAt)
+		if err != nil && source.FetchedAt != "" {
+			// Log warning or return error depending on policy
+			return scan.ThreatSourceStatus{}, err
+		}
 		out = append(out, scan.ThreatSourceStatus{
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cache/cache.go` at line 619, The parseTime function call on the
fetchedAt assignment is silently discarding the error return value using the
blank identifier, which means malformed timestamp data in source.FetchedAt will
result in a zero time value with no indication of failure. Modify the code to
capture both return values from parseTime, check if an error was returned, and
either log the error, return an error from the containing function, or otherwise
handle the parse failure appropriately to prevent silent data corruption.

Comment on lines +916 to +923
if i+1 <= len(source) {
if decoded, ok := parseHexRunes(source, i, 2); ok {
value.WriteRune(decoded)
raw.WriteString(string(source[i : i+2]))
i += 2
column += 2
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Off-by-one error in hex escape validation.

Line 916 checks i+1 <= len(source) before reading 2 hex digits at positions i and i+1, but this allows reading source[i+1] when i+1 == len(source), which is out of bounds. The check should be i+2 <= len(source).

🐛 Proposed fix
 		case 'x':
-			if i+1 <= len(source) {
+			if i+2 <= len(source) {
 				if decoded, ok := parseHexRunes(source, i, 2); ok {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/node/jsanalysis/jsanalysis.go` around lines 916 - 923, The boundary
check before reading 2 hex digits in the hex escape handling logic contains an
off-by-one error. The condition `i+1 <= len(source)` only validates that
position `i+1` is within bounds, but reading at positions `i` and `i+1` requires
ensuring both indices are valid. Change the condition from `i+1 <= len(source)`
to `i+2 <= len(source)` to properly validate that there are 2 characters
available to read starting from position `i`.

Comment thread internal/threat/threat.go
Comment on lines +335 to +342
func do(ctx context.Context, opts Options, req *http.Request) ([]byte, http.Header, error) {
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
ctx, cancel := context.WithTimeout(req.Context(), timeout)
defer cancel()
req = req.WithContext(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Context parameter shadowed before use.

The ctx parameter at line 335 is never used before being overwritten at line 340. The function creates a new timeout context from req.Context() instead. This triggers staticcheck SA4009 (argument overwritten before first use).

🔧 Proposed fix to use the passed context parameter
 func do(ctx context.Context, opts Options, req *http.Request) ([]byte, http.Header, error) {
 	timeout := opts.Timeout
 	if timeout <= 0 {
 		timeout = defaultTimeout
 	}
-	ctx, cancel := context.WithTimeout(req.Context(), timeout)
+	timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
 	defer cancel()
-	req = req.WithContext(ctx)
+	req = req.WithContext(timeoutCtx)
 
 	var lastErr error

This ensures the passed ctx parameter is respected as the parent context for the timeout.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func do(ctx context.Context, opts Options, req *http.Request) ([]byte, http.Header, error) {
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
ctx, cancel := context.WithTimeout(req.Context(), timeout)
defer cancel()
req = req.WithContext(ctx)
func do(ctx context.Context, opts Options, req *http.Request) ([]byte, http.Header, error) {
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req = req.WithContext(timeoutCtx)
var lastErr error
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 335-335: SA4009: argument ctx is overwritten before first use

(staticcheck)


[error] 340-340: SA4009(related information): assignment to ctx

(staticcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/threat/threat.go` around lines 335 - 342, The `ctx` parameter in the
`do` function is shadowed before being used, as a new timeout context is created
from `req.Context()` instead of the passed `ctx`. Fix this by modifying the
context.WithTimeout call to use the passed `ctx` parameter as the parent context
instead of `req.Context()`. This ensures the passed context parameter is
properly respected as the base context for creating the timeout context, which
may carry important deadlines or values from the caller.

Source: Linters/SAST tools

Store project snapshots as compact gzip files and resolve the latest scan through a small pointer document. Preserve legacy JSON reads, remove the redundant file index, and cap retained history with a configurable limit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant