Skip to content

Fix Zip-Slip arbitrary file write bypass via startswith #157

Open
Th-Shivam wants to merge 3 commits into
Stanzin7:masterfrom
Th-Shivam:fix-zip-slip
Open

Fix Zip-Slip arbitrary file write bypass via startswith #157
Th-Shivam wants to merge 3 commits into
Stanzin7:masterfrom
Th-Shivam:fix-zip-slip

Conversation

@Th-Shivam
Copy link
Copy Markdown
Contributor

@Th-Shivam Th-Shivam commented Apr 4, 2026

Description:

Fixes : #156
This PR patches an incomplete Zip-slip validation in the file extraction logic within src/extension_shield/utils/extension.py.

Fix details:
The original implementation used if not abs_target.startswith(abs_extract): to prevent extracting files outside of extract_dir. Since .startswith() matches prefixes exactly as strings, an extract_dir of /extensions/extracted_123 would falsely allow writing to /extensions/extracted_123_hacked/malicious_file.py because the path string technically still starts with /extensions/extracted_123.

This has been updated to use os.path.commonpath([abs_extract, abs_target]) != abs_extract which robustly compares the actual file system paths correctly, enforcing that extracted files reside strictly within the intended extract_dir directory structure.

Summary by CodeRabbit

Release Notes

  • Documentation

    • Updated Supabase CLI installation prerequisites for local database commands.
  • Chores

    • Refactored build scripts to use a wrapper interface for database operations.
    • Enhanced security in file extraction handling.
    • Improved backend version tracking and feedback submission.

Copilot AI review requested due to automatic review settings April 4, 2026 12:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a Zip-Slip path validation bypass in the extension extraction utility, and also extends scan feedback handling to persist model/ruleset version metadata derived from stored scan payloads.

Changes:

  • Harden zip-slip protection in extract_extension_crx() by switching from startswith() to os.path.commonpath() comparisons.
  • Persist model_version in generated executive summaries and persist ruleset_version into stored scan summary payloads.
  • Enhance /api/feedback to resolve and store model_version / ruleset_version, with new endpoint tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/extension_shield/utils/extension.py Replaces fragile prefix-string validation with commonpath-based zip-slip validation during extraction.
src/extension_shield/api/main.py Adds version-extraction helpers; augments stored scan summary with ruleset_version; persists versions when saving feedback.
src/extension_shield/core/summary_generator.py Ensures executive summary payloads default model_version to the model used.
tests/api/test_feedback_endpoint.py Adds tests asserting feedback persistence includes version metadata (and handles missing metadata).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 241 to 246
target_path = os.path.join(extract_dir, member.filename)
abs_target = os.path.abspath(target_path)
abs_extract = os.path.abspath(extract_dir)
if not abs_target.startswith(abs_extract):
if os.path.commonpath([abs_extract, abs_target]) != abs_extract:
raise ValueError(f"Zip-slip attempt detected: {member.filename}")
zip_ref.extract(member, extract_dir)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

os.path.commonpath() can raise ValueError on Windows when paths are on different drives (e.g., a crafted member filename like D:\...). That would turn a zip-slip attempt into an unhandled exception rather than a clean rejection. Consider wrapping the commonpath call in try/except ValueError and treating it as a zip-slip violation (raise the same ValueError you raise now).

Copilot uses AI. Check for mistakes.
Comment on lines 262 to 267
target_path = os.path.join(extract_dir, member.filename)
abs_target = os.path.abspath(target_path)
abs_extract = os.path.abspath(extract_dir)
if not abs_target.startswith(abs_extract):
if os.path.commonpath([abs_extract, abs_target]) != abs_extract:
raise ValueError(f"Zip-slip attempt detected: {member.filename}")
zip_ref.extract(member, extract_dir)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

Same commonpath check is used in the .zip extraction branch and has the same potential ValueError edge case on Windows (different drive letters). Handle ValueError consistently here as well so zip-slip attempts are rejected deterministically.

Copilot uses AI. Check for mistakes.
Comment on lines 241 to 246
target_path = os.path.join(extract_dir, member.filename)
abs_target = os.path.abspath(target_path)
abs_extract = os.path.abspath(extract_dir)
if not abs_target.startswith(abs_extract):
if os.path.commonpath([abs_extract, abs_target]) != abs_extract:
raise ValueError(f"Zip-slip attempt detected: {member.filename}")
zip_ref.extract(member, extract_dir)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The zip-slip fix is security-critical, but there isn’t currently a unit test that would have caught the original .startswith() prefix-bypass (e.g., extracting to a sibling dir like .../extracted_x_hacked). There are already extraction tests in tests/test_extension_extract.py (zip-bomb), so adding a focused regression test for this path-prefix case would help prevent reintroductions.

Copilot uses AI. Check for mistakes.
Comment on lines +987 to +1007
def _coerce_json_dict(value: Any) -> Dict[str, Any]:
"""Convert JSON-ish values to a dict for metadata lookups."""
if isinstance(value, dict):
return value
if isinstance(value, str):
try:
parsed = json.loads(value)
except (TypeError, ValueError):
return {}
return parsed if isinstance(parsed, dict) else {}
return {}


def _extract_feedback_versions(payload: Optional[Dict[str, Any]]) -> tuple[Optional[str], Optional[str]]:
"""Extract model and ruleset versions from a scan payload when available."""
if not isinstance(payload, dict):
return None, None

metadata = _coerce_json_dict(payload.get("metadata"))
summary = _coerce_json_dict(payload.get("summary"))
report_view_model = _coerce_json_dict(payload.get("report_view_model"))
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

This PR’s description is focused on the zip-slip validation change, but it also introduces new feedback-version extraction logic and modifies summary generation to persist model_version/ruleset_version. Please update the PR description to reflect these additional behavior changes, or split them into a separate PR to keep the security fix isolated and easier to review/release.

Copilot uses AI. Check for mistakes.
Comment on lines +1033 to +1041
def _resolve_feedback_versions(scan_id: str) -> tuple[Optional[str], Optional[str]]:
"""Look up the related scan payload and extract version metadata safely."""
payload = scan_results.get(scan_id)
if isinstance(payload, dict):
return _extract_feedback_versions(payload)

db_payload = db.get_scan_result(scan_id)
return _extract_feedback_versions(db_payload)

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

_resolve_feedback_versions() calls db.get_scan_result(scan_id) which (in both SQLite and Supabase implementations) fetches the full scan row (SELECT *). Since feedback only needs a couple metadata fields, this adds potentially heavy read I/O per feedback submission. Consider adding a narrower DB method/query (e.g., fetch only summary/metadata/scoring_v2) or storing the needed version fields in a dedicated column so this endpoint doesn’t need to load the full payload.

Copilot uses AI. Check for mistakes.
executive_summary.setdefault(
"ruleset_version",
scoring_v2_payload.get("weights_version") or scoring_v2_payload.get("scoring_version"),
)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

executive_summary is copied and augmented with ruleset_version before being stored in raw_results["summary"], but build_report_view_model_safe() is still fed analysis_results with executive_summary sourced from final_state (without this augmentation). That can lead to inconsistencies between summary and report_view_model for the same scan. Consider passing the updated executive_summary into the analysis_results dict used for report_view_model generation as well.

Suggested change
)
)
analysis_results = dict(analysis_results or {})
analysis_results["executive_summary"] = executive_summary

Copilot uses AI. Check for mistakes.
@sapnilbiswas
Copy link
Copy Markdown
Collaborator

@Th-Shivam please fix the failing checks!

@sapnilbiswas sapnilbiswas added the enhancement New feature or request label Apr 7, 2026
@Th-Shivam
Copy link
Copy Markdown
Contributor Author

@sapnilbiswas ok i will fix it

@github-actions github-actions Bot added the area: backend Changes to the Python backend and scanning pipeline label Apr 8, 2026
@sapnilbiswas
Copy link
Copy Markdown
Collaborator

@Th-Shivam Any updates?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Changes establish a local Supabase CLI wrapper script, add version tracking utilities to the feedback workflow, enhance zip-slip protection in extension extraction, and introduce a comprehensive feedback endpoint test suite. No breaking changes to public APIs or user-facing behavior.

Changes

Cohort / File(s) Summary
Supabase CLI Integration
docs/GET_STARTED.md, package.json, scripts/README.md, scripts/run-supabase.sh, scripts/supabase_push_env.sh
Introduces local wrapper script (run-supabase.sh) to check CLI availability before execution, rewires all npm Supabase scripts to use wrapper instead of direct CLI calls, removes supabase from devDependencies, and updates documentation to reflect external CLI requirement.
Version Tracking in Feedback
src/extension_shield/api/main.py, src/extension_shield/core/summary_generator.py
Adds helper functions to extract and resolve model_version and ruleset_version from scan payloads, enriches executive summary with ruleset version, and passes resolved versions to feedback persistence instead of hardcoded None values.
Security Enhancement
src/extension_shield/utils/extension.py
Replaces string-based zip-slip prefix check with os.path.commonpath() equality comparison for more robust path containment validation in CRX/ZIP extraction.
Test Coverage
tests/api/test_feedback_endpoint.py
Adds new feedback endpoint test suite verifying version tracking behavior and feedback persistence with and without version fields in scan payloads.

Sequence Diagram

sequenceDiagram
    participant Client
    participant API as API Main
    participant DB as Database
    participant SummaryGen as Summary Generator
    
    Client->>API: POST /feedback (scan_id, feedback_data)
    API->>API: _extract_feedback_versions(payload)
    alt Versions in payload
        API->>API: Parse model_version from summary
        API->>API: Extract ruleset_version from scoring_v2_payload
    else Versions not in payload
        API->>DB: get_scan_result(scan_id)
        DB-->>API: scan_result
        API->>API: _resolve_feedback_versions(scan_id)
    end
    API->>DB: save_feedback(model_version, ruleset_version, ...)
    DB-->>API: Feedback saved
    API->>SummaryGen: generate_summary()
    SummaryGen->>SummaryGen: Augment with model_version from model_name
    SummaryGen-->>API: enriched_summary
    API-->>Client: HTTP 200 + summary
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A wrapper script hops into view,
Checking if Supabase CLI is true,
Versions now tracked through the feedback flow,
Zip-slip protection—safer we go!
Tests ensure all paths stay bright,
Scripts and shields aligned just right! 🛡️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% 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 describes the primary security fix in the changeset: replacing a flawed startswith() check with os.path.commonpath() to prevent zip-slip attacks.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added area: docs Documentation-only changes area: infra CI, deployment, database, or repository automation changes area: tests Changes to test coverage or test infrastructure bug Bug report or bug fix related work documentation Documentation request or docs-focused work security Security-sensitive issue or change labels Apr 24, 2026
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: 3

🧹 Nitpick comments (2)
src/extension_shield/utils/extension.py (1)

239-247: Optional: centralize the containment guard to avoid logic drift.

The same zip-slip check is duplicated in both branches. A small helper would keep behavior consistent if this evolves later.

♻️ Suggested refactor
+def _assert_within_extract_dir(extract_dir: str, member_name: str) -> None:
+    target_path = os.path.join(extract_dir, member_name)
+    abs_target = os.path.abspath(target_path)
+    abs_extract = os.path.abspath(extract_dir)
+    if os.path.commonpath([abs_extract, abs_target]) != abs_extract:
+        raise ValueError(f"Zip-slip attempt detected: {member_name}")
+
 ...
-                    target_path = os.path.join(extract_dir, member.filename)
-                    abs_target = os.path.abspath(target_path)
-                    abs_extract = os.path.abspath(extract_dir)
-                    if os.path.commonpath([abs_extract, abs_target]) != abs_extract:
-                        raise ValueError(f"Zip-slip attempt detected: {member.filename}")
+                    _assert_within_extract_dir(extract_dir, member.filename)
                     zip_ref.extract(member, extract_dir)

 ...
-                    target_path = os.path.join(extract_dir, member.filename)
-                    abs_target = os.path.abspath(target_path)
-                    abs_extract = os.path.abspath(extract_dir)
-                    if os.path.commonpath([abs_extract, abs_target]) != abs_extract:
-                        raise ValueError(f"Zip-slip attempt detected: {member.filename}")
+                    _assert_within_extract_dir(extract_dir, member.filename)
                     zip_ref.extract(member, extract_dir)

Also applies to: 260-268

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extension_shield/utils/extension.py` around lines 239 - 247, Duplicate
zip-slip containment logic around the zip extraction should be centralized into
a small helper to avoid drift: create a helper function (e.g.,
_ensure_within_extract_dir(abs_extract, target_path) or
_is_within_extract_dir(extract_dir, member_filename)) and replace the duplicated
checks in the extraction loops (the code that computes target_path, abs_target,
abs_extract and compares os.path.commonpath) with a single call to that helper
before calling zip_ref.extract(member, extract_dir); ensure the helper raises
the same ValueError message ("Zip-slip attempt detected: {member.filename}") or
returns False so callers enforce the same behavior.
package.json (1)

4-12: Consider preserving a pinned Supabase CLI for reproducible tooling.

With supabase removed from devDependencies, script behavior now depends on each machine’s globally installed CLI version. If deterministic dev/CI behavior is required, keep a pinned local CLI and run through npx --no-install (or equivalent local-bin resolution).

#!/bin/bash
# Verify current package.json Supabase script wiring and whether Supabase is pinned locally.
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("package.json").read_text())
deps = pkg.get("devDependencies", {})
print("devDependencies.supabase:", deps.get("supabase"))
print("\nScripts using run-supabase.sh:")
for name, cmd in pkg.get("scripts", {}).items():
    if "run-supabase.sh" in cmd:
        print(f"  {name}: {cmd}")
PY

Expected: devDependencies.supabase is None/missing, and all db/supabase scripts route via run-supabase.sh.

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 4 - 12, Add a pinned supabase CLI to
devDependencies and ensure scripts use the local CLI resolver rather than
relying on a global install: add a fixed version entry for "supabase" under
devDependencies, and update the script entries that reference run-supabase.sh
(e.g., the "supabase", "db:diff", "db:push", "db:pull", "db:reset", "db:start",
"db:stop", "db:migration:up", "db:migration:new" scripts) to invoke the CLI via
local-bin resolution such as using "npx --no-install supabase ..." or the
package-manager's equivalent so CI/dev environments use the pinned version while
still keeping the helper run-supabase.sh behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/extension_shield/api/main.py`:
- Around line 1000-1030: _extract_feedback_versions can return non-string values
even though its signature is tuple[Optional[str], Optional[str]]; coerce the
resolved model_version and ruleset_version to either a stripped string or None
before returning so callers (e.g., db.save_feedback) never receive non-string
types. Locate the variables model_version and ruleset_version in
_extract_feedback_versions and replace the final return with normalization that:
if the value is None leave it None, otherwise convert to str(value).strip() and
treat empty strings as None.
- Around line 1033-1040: The DB read in _resolve_feedback_versions can raise and
crash /api/feedback; wrap the call to db.get_scan_result(scan_id) in a
try/except and on any exception return (None, None) so feedback persists; locate
_resolve_feedback_versions and ensure exceptions from db.get_scan_result are
caught and handled (still call _extract_feedback_versions only when db_payload
is valid) and return (None, None) on failure.

In `@src/extension_shield/core/summary_generator.py`:
- Line 566: The current use of summary.setdefault("model_version", model_name)
only fills the fallback when the key is missing, not when the value is
None/empty; change the logic in the summary generation path (the code using
summary.setdefault and the variables summary and model_name) to explicitly check
for falsy/empty values (e.g., if not summary.get("model_version"):
summary["model_version"] = model_name) so that None or empty-string values are
replaced with model_name.

---

Nitpick comments:
In `@package.json`:
- Around line 4-12: Add a pinned supabase CLI to devDependencies and ensure
scripts use the local CLI resolver rather than relying on a global install: add
a fixed version entry for "supabase" under devDependencies, and update the
script entries that reference run-supabase.sh (e.g., the "supabase", "db:diff",
"db:push", "db:pull", "db:reset", "db:start", "db:stop", "db:migration:up",
"db:migration:new" scripts) to invoke the CLI via local-bin resolution such as
using "npx --no-install supabase ..." or the package-manager's equivalent so
CI/dev environments use the pinned version while still keeping the helper
run-supabase.sh behavior.

In `@src/extension_shield/utils/extension.py`:
- Around line 239-247: Duplicate zip-slip containment logic around the zip
extraction should be centralized into a small helper to avoid drift: create a
helper function (e.g., _ensure_within_extract_dir(abs_extract, target_path) or
_is_within_extract_dir(extract_dir, member_filename)) and replace the duplicated
checks in the extraction loops (the code that computes target_path, abs_target,
abs_extract and compares os.path.commonpath) with a single call to that helper
before calling zip_ref.extract(member, extract_dir); ensure the helper raises
the same ValueError message ("Zip-slip attempt detected: {member.filename}") or
returns False so callers enforce the same behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ebb3e6e6-6641-419e-9763-93f2f460dae9

📥 Commits

Reviewing files that changed from the base of the PR and between 3a41aeb and 25076ac.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • .codex
  • docs/GET_STARTED.md
  • package.json
  • scripts/README.md
  • scripts/run-supabase.sh
  • scripts/supabase_push_env.sh
  • src/extension_shield/api/main.py
  • src/extension_shield/core/summary_generator.py
  • src/extension_shield/utils/extension.py
  • tests/api/test_feedback_endpoint.py

Comment on lines +1000 to +1030
def _extract_feedback_versions(payload: Optional[Dict[str, Any]]) -> tuple[Optional[str], Optional[str]]:
"""Extract model and ruleset versions from a scan payload when available."""
if not isinstance(payload, dict):
return None, None

metadata = _coerce_json_dict(payload.get("metadata"))
summary = _coerce_json_dict(payload.get("summary"))
report_view_model = _coerce_json_dict(payload.get("report_view_model"))
report_meta = _coerce_json_dict(report_view_model.get("meta"))

scoring_v2 = _coerce_json_dict(payload.get("scoring_v2"))
if not scoring_v2:
scoring_v2 = _coerce_json_dict(summary.get("scoring_v2"))

model_version = (
summary.get("model_version")
or metadata.get("model_version")
or report_meta.get("model_version")
or summary.get("llm_model")
or metadata.get("llm_model")
)

ruleset_version = (
summary.get("ruleset_version")
or metadata.get("ruleset_version")
or report_meta.get("ruleset_version")
or scoring_v2.get("weights_version")
or scoring_v2.get("scoring_version")
)

return model_version, ruleset_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 | 🟡 Minor

Normalize extracted version fields to strings to match declared return type.

_extract_feedback_versions is annotated as tuple[Optional[str], Optional[str]], but current selection logic can return non-string values from payloads. Normalize/strip before returning to avoid type drift into db.save_feedback.

🔧 Proposed fix
+def _coerce_optional_str(value: Any) -> Optional[str]:
+    if isinstance(value, str):
+        value = value.strip()
+        return value or None
+    return None
+
 def _extract_feedback_versions(payload: Optional[Dict[str, Any]]) -> tuple[Optional[str], Optional[str]]:
@@
-    return model_version, ruleset_version
+    return _coerce_optional_str(model_version), _coerce_optional_str(ruleset_version)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extension_shield/api/main.py` around lines 1000 - 1030,
_extract_feedback_versions can return non-string values even though its
signature is tuple[Optional[str], Optional[str]]; coerce the resolved
model_version and ruleset_version to either a stripped string or None before
returning so callers (e.g., db.save_feedback) never receive non-string types.
Locate the variables model_version and ruleset_version in
_extract_feedback_versions and replace the final return with normalization that:
if the value is None leave it None, otherwise convert to str(value).strip() and
treat empty strings as None.

Comment on lines +1033 to +1040
def _resolve_feedback_versions(scan_id: str) -> tuple[Optional[str], Optional[str]]:
"""Look up the related scan payload and extract version metadata safely."""
payload = scan_results.get(scan_id)
if isinstance(payload, dict):
return _extract_feedback_versions(payload)

db_payload = db.get_scan_result(scan_id)
return _extract_feedback_versions(db_payload)
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 | 🟠 Major

Guard feedback version lookup against DB read failures.

At Line 1039, an exception from db.get_scan_result(scan_id) will bubble up and fail /api/feedback. This path should degrade to (None, None) so feedback still persists.

🛡️ Proposed fix
 def _resolve_feedback_versions(scan_id: str) -> tuple[Optional[str], Optional[str]]:
     """Look up the related scan payload and extract version metadata safely."""
     payload = scan_results.get(scan_id)
     if isinstance(payload, dict):
         return _extract_feedback_versions(payload)

-    db_payload = db.get_scan_result(scan_id)
-    return _extract_feedback_versions(db_payload)
+    try:
+        db_payload = db.get_scan_result(scan_id)
+    except Exception as exc:
+        logger.warning("Failed to resolve feedback versions for scan_id=%s: %s", scan_id, exc)
+        return None, None
+    return _extract_feedback_versions(db_payload)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extension_shield/api/main.py` around lines 1033 - 1040, The DB read in
_resolve_feedback_versions can raise and crash /api/feedback; wrap the call to
db.get_scan_result(scan_id) in a try/except and on any exception return (None,
None) so feedback persists; locate _resolve_feedback_versions and ensure
exceptions from db.get_scan_result are caught and handled (still call
_extract_feedback_versions only when db_payload is valid) and return (None,
None) on failure.

)


summary.setdefault("model_version", model_name)
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

model_version fallback should handle empty/null values, not just missing keys.

At Line 566, setdefault skips fallback when "model_version" exists but is None/empty, which can still propagate missing version metadata.

🔧 Proposed fix
-                summary.setdefault("model_version", model_name)
+                if not isinstance(summary.get("model_version"), str) or not summary.get("model_version").strip():
+                    summary["model_version"] = model_name
📝 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
summary.setdefault("model_version", model_name)
if not isinstance(summary.get("model_version"), str) or not summary.get("model_version").strip():
summary["model_version"] = model_name
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extension_shield/core/summary_generator.py` at line 566, The current use
of summary.setdefault("model_version", model_name) only fills the fallback when
the key is missing, not when the value is None/empty; change the logic in the
summary generation path (the code using summary.setdefault and the variables
summary and model_name) to explicitly check for falsy/empty values (e.g., if not
summary.get("model_version"): summary["model_version"] = model_name) so that
None or empty-string values are replaced with model_name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: backend Changes to the Python backend and scanning pipeline area: docs Documentation-only changes area: infra CI, deployment, database, or repository automation changes area: tests Changes to test coverage or test infrastructure bug Bug report or bug fix related work documentation Documentation request or docs-focused work enhancement New feature or request security Security-sensitive issue or change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ecurity Vulnerability: Incomplete Zip-Slip Protection Allows Extraction to Adjacent Directories via Path Traversal

3 participants