Skip to content

fix(security): resolve open CodeQL code-scanning alerts#19

Merged
adityathebe merged 1 commit into
mainfrom
claude/session-4s9o99
Jul 21, 2026
Merged

fix(security): resolve open CodeQL code-scanning alerts#19
adityathebe merged 1 commit into
mainfrom
claude/session-4s9o99

Conversation

@adityathebe

@adityathebe adityathebe commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

Resolves the open CodeQL code-scanning alerts on main across four categories.

Path traversal — "Uncontrolled data used in path expression" (High) — pkg/cli/permission_catalog.go

The dir/cwd query parameter of the permission-catalog HTTP handler flowed straight into os.ReadFile/os.Stat/os.ReadDir. An attacker could pass dir=../../etc (or an absolute path) to enumerate arbitrary directories. Added resolveCatalogDir, which resolves the value against the workspace root and rejects anything that escapes it (using a cleaned-path prefix containment check) before any filesystem access. The handler now returns 400 for invalid input.

Integer overflow — "Size computation for allocation may overflow" (High)

make() size/capacity hints computed by adding two lengths (len(a)+len(b), len(x)+1) were flagged as potentially overflowing. Added a small collections.SafeAdd helper that saturates at math.MaxInt under a proper overflow guard, and routed all flagged sites through it — preserving the original capacity hints:

  • pkg/collections/fuzzy.go (Levenshtein matrix)
  • pkg/api/model.go (Candidates)
  • pkg/cli/prompt_entity.go (mergeStringMaps, mergeToolModes, mergePresets)

Shell injection — "Shell command built from environment values" (Medium) — pkg/cli/webapp/vite.config.ts

The build-time git() helper interpolated a filesystem-derived path into a shell command string via execSync. Switched to execFileSync with an argv array so no shell is spawned.

Workflow hardening — "Workflow does not contain permissions" (Medium) — .github/workflows/test.yml

Added a least-privilege top-level permissions: contents: read block.

Testing

  • go build / go test pass for the affected packages (collections, api, cli).
  • Added TestResolveCatalogDir covering the allowed and rejected (traversal / absolute) cases.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved permission catalog directory handling by blocking path traversal outside the workspace.
    • Invalid catalog paths now return a clear error instead of being processed.
    • Improved protection against integer overflow during internal capacity and allocation calculations.
    • Hardened build-time Git metadata retrieval.
  • Security

    • Restricted GitHub Actions token permissions to read-only repository contents.

Address the open CodeQL findings on main:

- Path traversal (High): confine the untrusted `dir`/`cwd` query
  parameter of the permission-catalog handler to the workspace root via
  resolveCatalogDir, rejecting `../` and absolute escapes before any
  filesystem access.
- Allocation size overflow (High): route make() size/capacity hints that
  add two lengths through a new collections.SafeAdd helper that saturates
  at math.MaxInt instead of wrapping (fuzzy matrix, model candidates, and
  the prompt-entity merge helpers).
- Shell command from environment values (Medium): build the git command
  in vite.config.ts with execFileSync + an argv array so no shell is
  spawned.
- Workflow permissions (Medium): add a least-privilege top-level
  `permissions: contents: read` block to the Test workflow.

Adds a resolveCatalogDir unit test covering the traversal cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186pQzdDXNY1jsbozM7Ed3B
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Allocation safety

Layer / File(s) Summary
Overflow-safe allocation calculations
pkg/collections/alloc.go, pkg/api/model.go, pkg/cli/prompt_entity.go, pkg/collections/fuzzy.go
Adds saturating SafeAdd and uses it for slice, map, and Levenshtein matrix capacity calculations.

Security hardening

Layer / File(s) Summary
Permission catalog path validation
pkg/cli/permission_catalog.go, pkg/cli/permission_catalog_test.go
Validates catalog directories remain within the workspace and returns HTTP 400 for invalid paths, with tests for valid and escaping inputs.
Command and workflow permission tightening
pkg/cli/webapp/vite.config.ts, .github/workflows/test.yml
Switches Git execution to argv-based execFileSync and restricts workflow token permissions to contents: read.

Suggested reviewers: moshloop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the security-focused CodeQL fixes across the change set.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/session-4s9o99
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/session-4s9o99

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.

@adityathebe
adityathebe enabled auto-merge (squash) July 21, 2026 08:05

@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: 2

🧹 Nitpick comments (1)
pkg/collections/alloc.go (1)

5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add boundary tests for SafeAdd.

Please cover normal addition, math.MaxInt + 1, and negative operands so the saturation contract remains protected.

🤖 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 `@pkg/collections/alloc.go` around lines 5 - 16, Add boundary-focused tests for
SafeAdd covering ordinary addition, saturation when adding 1 to math.MaxInt, and
either negative operand; assert each result matches the documented saturation
contract.
🤖 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 `@pkg/cli/permission_catalog.go`:
- Around line 55-57: Update the path validation condition to append the path
separator to base only when base does not already end with one, while preserving
the existing target != base check and workspace-escape rejection. Apply this in
the validation logic surrounding the target and base variables so root paths
such as / and C:\ correctly allow valid subdirectories.

In `@pkg/collections/fuzzy.go`:
- Around line 42-44: Update the inner loop in the matrix initialization to use
SafeAdd(len(s2), 1) as its range bound, matching the allocation of each matrix
row. Keep the existing initialization behavior unchanged while ensuring the loop
cannot overflow independently of the allocated row size.

---

Nitpick comments:
In `@pkg/collections/alloc.go`:
- Around line 5-16: Add boundary-focused tests for SafeAdd covering ordinary
addition, saturation when adding 1 to math.MaxInt, and either negative operand;
assert each result matches the documented saturation contract.
🪄 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

Run ID: 0f60c42e-bcaf-4e44-87af-57edc075f7bc

📥 Commits

Reviewing files that changed from the base of the PR and between be598f8 and ce7242a.

📒 Files selected for processing (8)
  • .github/workflows/test.yml
  • pkg/api/model.go
  • pkg/cli/permission_catalog.go
  • pkg/cli/permission_catalog_test.go
  • pkg/cli/prompt_entity.go
  • pkg/cli/webapp/vite.config.ts
  • pkg/collections/alloc.go
  • pkg/collections/fuzzy.go

Comment on lines +55 to +57
if target != base && !strings.HasPrefix(target, base+string(os.PathSeparator)) {
return "", fmt.Errorf("dir %q escapes workspace root", dir)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix path validation when baseCwd is a filesystem root.

If base is a root directory (e.g., / on Unix or C:\ on Windows), filepath.Clean(base) retains the trailing slash. Appending string(os.PathSeparator) results in a double slash (e.g., // or C:\\), which causes strings.HasPrefix to falsely reject valid subdirectories (e.g., /foo).

Ensure the separator is only appended if base does not already end with one.

🛠️ Proposed fix
-	if target != base && !strings.HasPrefix(target, base+string(os.PathSeparator)) {
+	prefix := base
+	if !strings.HasSuffix(prefix, string(os.PathSeparator)) {
+		prefix += string(os.PathSeparator)
+	}
+	if target != base && !strings.HasPrefix(target, prefix) {
 		return "", fmt.Errorf("dir %q escapes workspace root", dir)
 	}
📝 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
if target != base && !strings.HasPrefix(target, base+string(os.PathSeparator)) {
return "", fmt.Errorf("dir %q escapes workspace root", dir)
}
prefix := base
if !strings.HasSuffix(prefix, string(os.PathSeparator)) {
prefix += string(os.PathSeparator)
}
if target != base && !strings.HasPrefix(target, prefix) {
return "", fmt.Errorf("dir %q escapes workspace root", dir)
}
🤖 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 `@pkg/cli/permission_catalog.go` around lines 55 - 57, Update the path
validation condition to append the path separator to base only when base does
not already end with one, while preserving the existing target != base check and
workspace-escape rejection. Apply this in the validation logic surrounding the
target and base variables so root paths such as / and C:\ correctly allow valid
subdirectories.

Comment thread pkg/collections/fuzzy.go
Comment on lines +42 to +44
matrix := make([][]int, SafeAdd(len(s1), 1))
for i := range matrix {
matrix[i] = make([]int, len(s2)+1)
matrix[i] = make([]int, SafeAdd(len(s2), 1))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n== fuzzy.go outline ==\n'
ast-grep outline pkg/collections/fuzzy.go --view expanded || true

printf '\n== fuzzy.go relevant lines ==\n'
cat -n pkg/collections/fuzzy.go | sed -n '1,140p'

printf '\n== SafeAdd definitions/usages ==\n'
rg -n "func SafeAdd|SafeAdd\(" -S .

printf '\n== integer type context ==\n'
sed -n '1,120p' pkg/collections/fuzzy.go | cat -n

Repository: flanksource/captain

Length of output: 4496


🏁 Script executed:

set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('pkg/collections/fuzzy.go')
text = p.read_text()
print(text)
PY

printf '\n== SafeAdd definition(s) ==\n'
rg -n "func SafeAdd|type .*SafeAdd|SafeAdd =" -S pkg . || true

Repository: flanksource/captain

Length of output: 1542


🏁 Script executed:

set -euo pipefail

printf '--- fuzzy.go ---\n'
nl -ba pkg/collections/fuzzy.go | sed -n '1,120p'

printf '\n--- SafeAdd references ---\n'
rg -n "SafeAdd" pkg -S

printf '\n--- go.mod ---\n'
cat -n go.mod | sed -n '1,120p'

Repository: flanksource/captain

Length of output: 248


🏁 Script executed:

set -euo pipefail

nl -ba pkg/collections/alloc.go | sed -n '1,120p'

printf '\n== SafeAdd callers in collections ==\n'
rg -n "SafeAdd\(" pkg/collections -S

printf '\n== Any tests for fuzzy/alloc ==\n'
rg -n "Levenshtein|SafeAdd" -S . --glob '*_test.go'

Repository: flanksource/captain

Length of output: 197


🏁 Script executed:

set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('pkg/collections/alloc.go')
print(p.read_text())
PY

Repository: flanksource/captain

Length of output: 692


🏁 Script executed:

set -euo pipefail

cat -n pkg/collections/alloc.go | sed -n '1,120p'

Repository: flanksource/captain

Length of output: 803


Use SafeAdd for the inner loop bound too.
matrix[i] is sized with SafeAdd(len(s2), 1), but for j := range len(s2) + 1 can still wrap at MaxInt and leave the first-row initialization out of sync with the allocated row. Use the same helper here.

Proposed fix
-	for j := range len(s2) + 1 {
+	for j := range SafeAdd(len(s2), 1) {
🤖 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 `@pkg/collections/fuzzy.go` around lines 42 - 44, Update the inner loop in the
matrix initialization to use SafeAdd(len(s2), 1) as its range bound, matching
the allocation of each matrix row. Keep the existing initialization behavior
unchanged while ensuring the loop cannot overflow independently of the allocated
row size.

@adityathebe
adityathebe merged commit bf94d0c into main Jul 21, 2026
10 of 11 checks passed
@adityathebe
adityathebe deleted the claude/session-4s9o99 branch July 21, 2026 08:07
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.

2 participants