Skip to content

Harden cve-fix with pre-fix scanning, binary verification, PR dedup and input guardrails#41

Merged
amir-yogev-gh merged 20 commits into
flightctl:mainfrom
amir-yogev-gh:cve-fix-hardening
May 12, 2026
Merged

Harden cve-fix with pre-fix scanning, binary verification, PR dedup and input guardrails#41
amir-yogev-gh merged 20 commits into
flightctl:mainfrom
amir-yogev-gh:cve-fix-hardening

Conversation

@amir-yogev-gh
Copy link
Copy Markdown
Collaborator

Pre-fix CVE scanning:
Add /scan phase between /start and /patch that runs ecosystem-specific vulnerability scanners to confirm a CVE is present before attempting fixes. Supports Go GOTOOLCHAIN matching to prevent false negatives.

Post-fix binary verification:
Enhance /validate with post-fix binary verification — for Go projects, builds the binary and runs govulncheck -mode binary to catch cases where replace directives or transitive overrides silently reintroduce the vulnerable version.

Existing PR deduplication:
Add existing-PR deduplication check to /pr — three-layer search (exact CVE, bot updates, same-package from other CVEs) prevents duplicate PRs.

Untrusted input guardrails:
Add untrusted input guardrails to guidelines and /start to prevent injection from Jira ticket content.

All deterministic operations are backed by Python scripts (stdlib-only) with inline fallback instructions in the skill files.

…and input guardrails

Add /scan phase between /start and /patch that runs ecosystem-specific
vulnerability scanners to confirm a CVE is present before attempting fixes.
Supports Go GOTOOLCHAIN matching to prevent false negatives.

Enhance /validate with post-fix binary verification — for Go projects,
builds the binary and runs govulncheck -mode binary to catch cases where
replace directives or transitive overrides silently reintroduce the
vulnerable version.

Add existing-PR deduplication check to /pr — three-layer search (exact
CVE, bot updates, same-package from other CVEs) prevents duplicate PRs.

Add untrusted input guardrails to guidelines and /start to prevent
injection from Jira ticket content.

All deterministic operations are backed by Python scripts (stdlib-only)
with inline fallback instructions in the skill files.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Review Change Stack

Warning

Rate limit exceeded

@amir-yogev-gh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 30 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: fcb6a804-e886-40af-93fb-92c0654569b6

📥 Commits

Reviewing files that changed from the base of the PR and between 6c1198b and 6bc0ea7.

📒 Files selected for processing (5)
  • cve-fix/scripts/_common.py
  • cve-fix/scripts/check_existing_prs.py
  • cve-fix/scripts/scan.py
  • cve-fix/scripts/verify.py
  • cve-fix/skills/validate.md

Walkthrough

This PR introduces a /scan phase to the CVE-fix workflow, positioned between /start and /patch. It adds three Python CLI tools (scan.py, verify.py, check_existing_prs.py) for scanning CVE presence, verifying fixes, and detecting existing PRs. Documentation is updated across workflow controller, skills, guidelines, and README to integrate the scan phase and enforce security practices for untrusted Jira input handling.

Changes

CVE-Fix Scan Phase Integration

Layer / File(s) Summary
Workflow Architecture & Phase Definition
AGENTS.md, cve-fix/SKILL.md, cve-fix/skills/controller.md, cve-fix/README.md, cve-fix/guidelines.md, cve-fix/skills/start.md, CONTRIBUTING.md
New /scan phase added between /start and /patch. Phase numbering and agent/skill metadata updated. Workflow recommendations route next steps by scan verdict (present → /patch, absent/informational → /close with VEX, in_base_image → escalate, scan_failed → cautious/manual). Security guidance added treating Jira content as untrusted.
Shared Script Utilities
cve-fix/scripts/_common.py
Shared helpers added: CVE regex validation, SCAN_TIMEOUT env handling, per-tool success-code mapping, run() wrapper, is_successful_scan(), write_json(), and timestamp() used by scan/verify scripts.
Scan CLI
cve-fix/scripts/scan.py
scan.py detects language (Go/Node/Python), runs ecosystem scanners (govulncheck, npm audit, pip-audit), extracts manifest/base-image evidence, applies retry/toolchain heuristics (Go), determines a verdict (present, present_by_version, in_base_image, informational, scan_failed, absent), optionally generates VEX justification, and writes scan-result.json.
Verify CLI
cve-fix/scripts/verify.py
verify.py re-scans post-fix: builds Go binaries and runs govulncheck -mode binary (fallbacks included), regenerates Node lockfile + npm audit, runs pip-audit for Python, classifies verdicts as fixed/still_present/scan_failed, and writes verify-result.json.
Existing PR Detection
cve-fix/scripts/check_existing_prs.py
check_existing_prs.py queries gh to find open PRs matching CVE ID, Dependabot/Renovate bot updates for the package, or package-related diffs; returns structured JSON with found and match_type, and uses exit codes 0/1/2 for match/none/error.
Runbooks, Commands, and Docs
cve-fix/skills/scan.md, cve-fix/commands/scan.md, cve-fix/skills/pr.md, cve-fix/skills/validate.md, cve-fix/skills/close.md, cve-fix/skills/start.md, cve-fix/guidelines.md
New scan runbook and command docs; PR runbook inserts an existing-PR check step; validate runbook adds post-fix verification step and validation-results scan section; close runbook supports fix-based vs VEX-based closures and templates; outputs and artifact paths updated.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the four main hardening features added in this PR: pre-fix CVE scanning, binary verification, PR deduplication, and input guardrails.
Description check ✅ Passed The description comprehensively covers all major changes in the PR, including the four core features (pre-fix scanning, post-fix verification, PR dedup, input guardrails) and implementation approach (Python scripts with fallback instructions).
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
cve-fix/scripts/verify.py (2)

56-102: 💤 Low value

Temp directory leaks if any step between mkdtemp and rmtree raises.

binary_dir is created on Line 56 but only cleaned up on Line 102 in linear flow — a subprocess exception or interrupt between those points leaves the temp dir behind. Wrap the build/scan block in try/finally (and move the import shutil to module scope).

♻️ Suggested fix
+import shutil
...
     binary_dir = tempfile.mkdtemp(prefix="cve-verify-")
-    scan_method = "binary"
-    scan_output = ""
-    scan_exit = 0
-
-    # Try building binaries
-    build_exit, build_out = run(
-        ["go", "build", "-o", f"{binary_dir}/", "./..."],
-        env=env,
-    )
-    ...
-    # Clean up
-    import shutil
-    shutil.rmtree(binary_dir, ignore_errors=True)
+    try:
+        ...
+    finally:
+        shutil.rmtree(binary_dir, ignore_errors=True)
🤖 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 `@cve-fix/scripts/verify.py` around lines 56 - 102, The temp dir created with
tempfile.mkdtemp (binary_dir) can leak if an exception/interrupt occurs before
shutil.rmtree is reached; wrap the build/scan logic that uses binary_dir (the
run(...) call that does go build, the binaries iteration with govulncheck, and
the fallback govulncheck calls) in a try/finally that always calls
shutil.rmtree(binary_dir, ignore_errors=True) in the finally block, and move the
import shutil to module scope so cleanup is available regardless of failures;
ensure variables like scan_exit/scan_output are initialized before the try so
their values remain defined after cleanup.

111-126: ⚖️ Poor tradeoff

npm install --package-lock-only mutates the working tree during verification.

The verify step is intended to be informational, but this rewrites package-lock.json in the user's repo. If the user then runs git diff/git status, the lockfile churn is conflated with the actual fix commit. Consider either (a) running the regeneration in a temp copy of the repo, (b) stashing/reverting the lockfile change after the audit, or (c) at minimum documenting that /validate may modify the lockfile so users can git-stash beforehand.

🤖 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 `@cve-fix/scripts/verify.py` around lines 111 - 126, The verify_node function
currently runs "npm install --package-lock-only" in-place which mutates
package-lock.json; change verify_node to avoid changing the working tree by
either (preferred) performing the regeneration and audit inside a temporary copy
of the repo (create a tempdir, copy the repo files (excluding .git) into it and
call run(["npm","install","--package-lock-only"], cwd=tmpdir) and
run(["npm","audit","--json"], cwd=tmpdir)), or (simpler) run the install
in-place but immediately revert the lockfile change with git (e.g., call
run(["git","checkout","--","package-lock.json"]) after the regen) so the working
tree is unmodified; update the run(...) invocations in verify_node accordingly
(use the cwd argument for tempdir or add the revert run call) and ensure
scan_method and exit codes are preserved.
cve-fix/scripts/check_existing_prs.py (1)

66-91: 💤 Low value

search_by_package substring match (lines 88, 70) can trigger false positives for common package tokens.

package in diff_out and --search package match any occurrence of the token in PR titles and diffs, not just dependency manifest lines. Short or common names like go, url, http, or path components like helm will match unrelated PRs. Consider restricting the diff match to manifest-file sections (e.g., require the package to appear on a go.mod, package.json, or requirements.txt line) or at minimum within a hunk header context.

🤖 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 `@cve-fix/scripts/check_existing_prs.py` around lines 66 - 91,
search_by_package currently uses a raw substring check ("package in diff_out")
and the gh "--search" token which yields false positives for short/common
tokens; update search_by_package to parse the PR diff returned by
gh("pr","diff",...) and only consider matches that occur in dependency/manifest
contexts or hunk headers (e.g., file path lines starting with "diff --git" /
"+++ b/" or hunk headers "@@") or within lines that look like manifest entries
(e.g., lines from go.mod, package.json, requirements.txt) and match the package
as a whole word (use regex word boundaries) instead of a plain substring; keep
the rest of the logic (calling gh, iterating candidates) but replace the simple
package substring test with a function that scans diff_out for the package in
those manifest/hunk contexts before returning the PR.
🤖 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 `@cve-fix/scripts/check_existing_prs.py`:
- Around line 110-154: The code currently only wraps search_by_cve in a
try/except, so gh failures in search_by_bot and search_by_package can crash or
be misinterpreted as "no match"; wrap each call to search_by_cve, search_by_bot,
and search_by_package in a try/except Exception as exc, and on any exception
emit the standardized JSON {"found": false, "match_type": "none", "error":
str(exc)} and return exit code 2; also ensure that the functions either raise on
gh non-zero failures or that you detect and treat a special error return
consistently (i.e., convert any gh-error sentinel into raising an Exception so
the centralized except block handles it).

In `@cve-fix/scripts/scan.py`:
- Around line 166-183: The function check_manifests currently returns
content.splitlines()[0] which is the file's first line, not the line that
actually contains the package; change it to search the file lines for the first
line where package.lower() is in line.lower() and return that matching line (or
"" if none), preserving the existing manifests dict, OSError handling, and
behavior of returning a single context string from check_manifests.
- Around line 206-231: The determine_verdict function currently assumes
govulncheck exit semantics when computing scanner_success from
scan_result["scan_exit_code"], which causes npm and pip-audit (which use exit
code 1 to indicate found vulns) to be misclassified as scan_failed; update
determine_verdict to read scan_result["scan_tool"] and normalize exit codes per
tool (e.g., treat exit 1 for "npm" and "pip-audit" as "vulns found"/successful
scan similar to govulncheck's 3), then compute scanner_success from the
normalized semantics and use that normalized value in the subsequent
manifest_match/base_images/scan_failed logic so Node/Python scans that report
vulnerabilities are not treated as failures; keep references to
determine_verdict, scan_result["scan_exit_code"], scan_result["scan_tool"],
scanner_success, and existing conditional branches when updating the logic.

In `@cve-fix/scripts/verify.py`:
- Around line 152-162: The determine_verdict function misclassifies scanners
that exit with code 1 (vulns found) as failures; update determine_verdict to
mirror scan.py's normalization: keep the initial check that non-zero exit with
empty output => "scan_failed" (i.e., if exit_code != 0 and not output.strip()
return "scan_failed"), but treat exit codes like 1 (and 3) as non-fatal when
output exists by changing the next condition to check exit_code not in (0,1,3)
(or otherwise normalize per scanner) before returning "scan_failed"; use the
existing symbols determine_verdict, scan_result["scan_exit_code"],
scan_result["scan_output"], and cve_id to implement this minimal adjustment so
scanners that ran successfully but found other vulns don't cause a false
"scan_failed".

In `@cve-fix/skills/validate.md`:
- Line 59: Replace the absolute path "/tmp/verify/" used in the Go build and
govulncheck examples with a relative artifact path (e.g.,
".artifacts/cve-fix/{context}/verify/") so the markdown follows the
relative-path rule and CI validate-structure.sh passes; update both the build
command (`go build -o /tmp/verify/ ./...`) and the scan command (`govulncheck
-mode binary /tmp/verify/{binary}` and fallback `govulncheck -show verbose
./...`) to use the new relative directory while keeping the same filenames and
placeholders.

---

Nitpick comments:
In `@cve-fix/scripts/check_existing_prs.py`:
- Around line 66-91: search_by_package currently uses a raw substring check
("package in diff_out") and the gh "--search" token which yields false positives
for short/common tokens; update search_by_package to parse the PR diff returned
by gh("pr","diff",...) and only consider matches that occur in
dependency/manifest contexts or hunk headers (e.g., file path lines starting
with "diff --git" / "+++ b/" or hunk headers "@@") or within lines that look
like manifest entries (e.g., lines from go.mod, package.json, requirements.txt)
and match the package as a whole word (use regex word boundaries) instead of a
plain substring; keep the rest of the logic (calling gh, iterating candidates)
but replace the simple package substring test with a function that scans
diff_out for the package in those manifest/hunk contexts before returning the
PR.

In `@cve-fix/scripts/verify.py`:
- Around line 56-102: The temp dir created with tempfile.mkdtemp (binary_dir)
can leak if an exception/interrupt occurs before shutil.rmtree is reached; wrap
the build/scan logic that uses binary_dir (the run(...) call that does go build,
the binaries iteration with govulncheck, and the fallback govulncheck calls) in
a try/finally that always calls shutil.rmtree(binary_dir, ignore_errors=True) in
the finally block, and move the import shutil to module scope so cleanup is
available regardless of failures; ensure variables like scan_exit/scan_output
are initialized before the try so their values remain defined after cleanup.
- Around line 111-126: The verify_node function currently runs "npm install
--package-lock-only" in-place which mutates package-lock.json; change
verify_node to avoid changing the working tree by either (preferred) performing
the regeneration and audit inside a temporary copy of the repo (create a
tempdir, copy the repo files (excluding .git) into it and call
run(["npm","install","--package-lock-only"], cwd=tmpdir) and
run(["npm","audit","--json"], cwd=tmpdir)), or (simpler) run the install
in-place but immediately revert the lockfile change with git (e.g., call
run(["git","checkout","--","package-lock.json"]) after the regen) so the working
tree is unmodified; update the run(...) invocations in verify_node accordingly
(use the cwd argument for tempdir or add the revert run call) and ensure
scan_method and exit codes are preserved.
🪄 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: Enterprise

Run ID: 9b6e723b-7cec-496f-9e20-0fc14eb5352b

📥 Commits

Reviewing files that changed from the base of the PR and between 35ed63f and 1e799a6.

📒 Files selected for processing (13)
  • AGENTS.md
  • cve-fix/README.md
  • cve-fix/SKILL.md
  • cve-fix/commands/scan.md
  • cve-fix/guidelines.md
  • cve-fix/scripts/check_existing_prs.py
  • cve-fix/scripts/scan.py
  • cve-fix/scripts/verify.py
  • cve-fix/skills/controller.md
  • cve-fix/skills/pr.md
  • cve-fix/skills/scan.md
  • cve-fix/skills/start.md
  • cve-fix/skills/validate.md

Comment thread cve-fix/scripts/check_existing_prs.py
Comment thread cve-fix/scripts/scan.py
Comment thread cve-fix/scripts/scan.py
Comment thread cve-fix/scripts/verify.py
Comment thread cve-fix/skills/validate.md Outdated
Wrap all three search layers in a unified try/except so that
subprocess.TimeoutExpired or OSError from gh CLI in Layers 2 and 3
produces the documented JSON error and exit code 2 instead of an
unhandled traceback misinterpreted as "no match found".
Return the actual line containing the package name rather than the
first line of the manifest file, so the scan result includes
meaningful context about the dependency.
npm audit and pip-audit exit with code 1 when vulnerabilities are
found (a successful scan), but the code assumed govulncheck semantics
where only 0 and 3 are success. Extract _is_successful_scan() that
normalizes exit codes per tool so Node.js and Python scans that find
vulnerabilities are not misclassified as scan_failed.
Same issue as scan.py: npm audit and pip-audit exit with code 1 when
vulnerabilities are found, which was misclassified as scan_failed.
Add _is_successful_scan() that normalizes exit codes per scan method
so post-fix verification correctly reports "fixed" when the target
CVE is gone but other vulnerabilities remain.
When /scan determines a CVE is absent, informational, or in a base
image, produce a structured VEX justification (following the 5 CSAF
types) that /close can paste directly into the Jira comment.

Three types are auto-detected: component not present, vulnerable code
not present, vulnerable code not in execute path (Go only). Two
require human judgment and are flagged as needs_human_review.

The /close phase now supports two paths: fix-based closure (with PRs)
and VEX-based closure (scan → close shortcut, no patch needed). The
controller routing is updated to recommend /close directly when the
scan verdict is absent or informational.
@amir-yogev-gh amir-yogev-gh requested a review from adalton May 11, 2026 16:13
Copy link
Copy Markdown
Contributor

@adalton adalton left a comment

Choose a reason for hiding this comment

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

Good feature set — pre-fix scanning, binary verification, PR dedup, and VEX justification are all the right additions. The skill markdown is well-structured and the inline fallback instructions are a nice portability pattern.

A few things to address before merging. Inline comments below are organized by severity: must-fix, should-fix, and suggestions.

Process note: The commit messages don't include Co-Authored-By trailers. If AI tooling was used to generate the Python scripts, our project convention expects that attribution.

Comment thread cve-fix/scripts/scan.py Outdated
Comment thread cve-fix/scripts/scan.py Outdated
Comment thread cve-fix/scripts/scan.py
Comment thread cve-fix/scripts/verify.py
Comment thread cve-fix/scripts/verify.py Outdated
Comment thread cve-fix/scripts/check_existing_prs.py Outdated
Comment thread cve-fix/scripts/scan.py Outdated
Comment thread cve-fix/scripts/scan.py
Comment thread cve-fix/scripts/scan.py Outdated
Comment thread cve-fix/scripts/verify.py Outdated
@adalton
Copy link
Copy Markdown
Contributor

adalton commented May 11, 2026

Should fix — code duplication between scan.py and verify.py

run() is duplicated verbatim across both files. _is_successful_scan() exists in both but uses different dict keys (scan_tool vs scan_method) to look up the same semantics — different key names for the same concept is a maintenance trap. CVE_PATTERN, SCAN_TIMEOUT, and the main-function boilerplate (arg parsing, os.chdir, JSON output, directory restore) are all duplicated.

A small _common.py module would eliminate this and make the key-name divergence impossible.


Suggestion — npm install --package-lock-only in verify.py modifies the working tree

If the patched lockfile was already committed as part of the fix, verify_node overwrites it. Worth documenting as a known side effect at minimum.


Suggestion — CONTRIBUTING.md exit code convention

CONTRIBUTING.md documents 0 = informational, 1 = halt, but check_existing_prs.py uses 0 = match found, 1 = no match, 2 = error. The convention doc should acknowledge that search-type scripts can define their own exit code semantics in their docstrings.

When the scanner runs successfully and confirms the CVE is absent,
but the project has a Dockerfile and the package is not in any
manifest, the verdict was incorrectly set to in_base_image instead
of absent. Guard the in_base_image check on scanner failure so that
a successful clean scan always reaches the absent verdict.
When CVE ID format is invalid or the target directory does not exist,
scan.py and verify.py now write a scan_failed JSON result and return
exit code 1 instead of silently returning 0 with no output. This
ensures the skill markdown always has structured JSON to parse.
…sion

When the scanner exits with an unknown error code (e.g., compilation
failure exit 2 from govulncheck), the situation is unknown — not
"present by version". Now returns scan_failed for tool-level crashes
instead of incorrectly claiming the CVE is likely present.
scan_go has a fallback that retries without GOTOOLCHAIN when the Go
version download fails, but verify_go was missing it. Both the build
step and the source-scan fallback now detect GOTOOLCHAIN failures
and retry without the env var, matching scan_go's behavior.
The temp directory created by verify_go could leak if an exception
or interrupt occurred between mkdtemp and rmtree. Wrap the build/scan
block in try/finally to guarantee cleanup and move the shutil import
to the top of the file.
…os.chdir

Centralizes run(), is_successful_scan(), CVE_PATTERN, SCAN_TIMEOUT,
write_json(), and timestamp() into _common.py. Both scan.py and
verify.py now import from this module instead of duplicating code.

Key changes:
- Unified is_successful_scan() uses scan_tool consistently (verify.py
  previously used scan_method — different key name for the same concept)
- Replaces os.chdir/restore pattern with subprocess cwd= parameter,
  eliminating fragile global state mutation
- SCAN_TIMEOUT parsing now handles ValueError with fallback to 300
- verify.py npm lockfile side effect documented in docstring
- Output truncation in scan.py now extracts CVE-relevant lines with
  context instead of blindly taking the first 30 lines
- check_base_images uses Dockerfile* glob for rhel8/rhel9 naming
The package substring match against the entire PR diff produced
false positives for short or common package names that appeared in
code comments, docs, or context lines. Now only matches against
+/- diff lines (excluding +++ and --- file headers).
Add a note in validate.md that npm install --package-lock-only
modifies the working tree's package-lock.json during verification.

Update CONTRIBUTING.md to acknowledge that search/query scripts
(like check_existing_prs.py) may define their own exit code semantics
in their docstrings, distinct from the report-script convention.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@cve-fix/scripts/check_existing_prs.py`:
- Around line 40-41: Several code paths that execute the GitHub CLI (`gh`)
currently swallow non-zero exits by returning None (seen at the return None
sites around the `gh` calls), which masks API/auth/rate-limit failures; change
those return-None branches to propagate a structured error: raise a specific
exception (e.g., GhQueryError) or return an error object containing the exit
code and stderr from the `gh` invocation in the functions that run the `gh`
command, and update `main()` to catch that exception/error and exit with code 2;
specifically replace the `return None` after `if code != 0:` in the functions
that invoke `gh` (the occurrences referenced around lines 40-41, 59-60, 90-91,
102-104) with raising/returning the structured error that includes the exit code
and message so `main()` can detect failures and return 2.

In `@cve-fix/scripts/scan.py`:
- Around line 355-358: The current join of work_dir = repo_dir / build_location
can escape the repository via paths like ../../; before using work_dir, resolve
it (e.g., resolved = (repo_dir / build_location).resolve()) and verify it is
contained within repo_dir.resolve() (e.g., check resolved == repo_dir.resolve()
or resolved.is_relative_to(repo_dir.resolve()) / startswith comparison), and if
the check fails call _write_error with the same signature (_write_error(...,
cve_id, package)) and return 1; keep the existing is_dir() check after this
containment validation.

In `@cve-fix/scripts/verify.py`:
- Around line 227-230: The code currently constructs work_dir = repo_dir /
build_location which allows path traversal via '..'; fix by resolving both
repo_dir and the candidate work_dir (use Path.resolve(strict=False)) and verify
the resolved work_dir is inside the resolved repo_dir (e.g., via
Path.is_relative_to or by comparing prefixes of their str() values / parents)
before proceeding; if the check fails call _write_error with a clear message and
return 1 (preserve the existing error-handling flow around work_dir, repo_dir,
build_location and _write_error).
- Around line 129-137: The current logic sets scan_tool to a non-canonical value
("npm_audit_stale_lockfile") when lockfile regen fails and that breaks
downstream success-code normalization which expects "npm_audit"; change the code
so scan_tool always remains "npm_audit" (regardless of regen_exit) and add a
separate field (e.g., "lockfile_regen_failed" boolean or
"lockfile_regen_status") to the returned dict to indicate regen failure; update
both places where regen_exit is checked (the block around regen_exit and the
similar block at lines ~174-176) to stop mutating scan_tool and instead set the
new status field while leaving scan_exit/scan_output behavior unchanged.

In `@cve-fix/skills/validate.md`:
- Around line 49-53: The markdown examples use mixed path
styles—`../scripts/verify.py`, `scripts/verify.py`, and `${TMPDIR}/verify/`—so
normalize them to relative paths: change all occurrences of `scripts/verify.py`
to `../scripts/verify.py` and replace `${TMPDIR}/verify/` (or any absolute/tmp
path) with the relative artifact path used elsewhere (e.g.,
`.artifacts/cve-fix/{context}` or `.artifacts/...`) so every command example
consistently uses relative paths and remains symlink-safe.
🪄 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: Enterprise

Run ID: d25a92dd-3df9-4906-a7e7-ac9e31412c37

📥 Commits

Reviewing files that changed from the base of the PR and between 1e799a6 and 6c1198b.

📒 Files selected for processing (11)
  • CONTRIBUTING.md
  • cve-fix/README.md
  • cve-fix/guidelines.md
  • cve-fix/scripts/_common.py
  • cve-fix/scripts/check_existing_prs.py
  • cve-fix/scripts/scan.py
  • cve-fix/scripts/verify.py
  • cve-fix/skills/close.md
  • cve-fix/skills/controller.md
  • cve-fix/skills/scan.md
  • cve-fix/skills/validate.md
✅ Files skipped from review due to trivial changes (3)
  • cve-fix/skills/controller.md
  • cve-fix/skills/scan.md
  • cve-fix/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • cve-fix/guidelines.md

Comment thread cve-fix/scripts/check_existing_prs.py Outdated
Comment thread cve-fix/scripts/scan.py Outdated
Comment thread cve-fix/scripts/verify.py
Comment thread cve-fix/scripts/verify.py Outdated
Comment thread cve-fix/skills/validate.md
Non-zero gh exit codes (auth errors, rate limits) were silently
treated as "no match found" because the search functions returned
None. Introduce GhQueryError and _gh_or_raise() so API failures
propagate to main() and produce the documented exit code 2.
A build_location like ../../etc could scan outside the target
repository. Add validate_work_dir() to _common.py that resolves
the path and verifies containment within repo_dir. scan.py now
rejects traversal attempts with an error-state JSON result.
Same containment check as scan.py: resolve the path and verify it
stays within repo_dir before scanning, rejecting traversal attempts.
The non-canonical value npm_audit_stale_lockfile was not recognized
by is_successful_scan(), causing valid npm audit runs (exit 1 with
findings) to be misclassified as scan_failed. Keep scan_tool as
npm_audit and report lockfile regeneration status in a separate
lockfile_regen_failed field.
Fix mixed path references: use ../scripts/verify.py consistently
(relative to skills/) and replace ${TMPDIR}/verify/ with the
relative artifact path .artifacts/cve-fix/{context}/verify/ for
symlink-safe behavior.
@amir-yogev-gh
Copy link
Copy Markdown
Collaborator Author

I ignored the ask to add "Co-Authored-By".
It's clear that AI is highly involved and additional crediting is not required.

@amir-yogev-gh amir-yogev-gh requested a review from adalton May 12, 2026 07:06
@amir-yogev-gh amir-yogev-gh merged commit f9fed65 into flightctl:main May 12, 2026
4 checks passed
@amir-yogev-gh amir-yogev-gh deleted the cve-fix-hardening branch May 12, 2026 14:36
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