Skip to content

Commit 6355405

Browse files
tirth8205claude
andcommitted
fix: critical git diff arg order, wiki path traversal, schema mismatch
- Fix git diff argument ordering: place base ref BEFORE -- separator, not after (-- means "paths follow", so base was treated as a filename) - Fix path traversal in get_wiki_page() fallback: validate resolved path stays within wiki directory using is_relative_to() - Fix wiki flow query: flow_memberships has no node_qualified_name column, JOIN through nodes table to get qualified_name - Tighten git ref validation from startswith("-") to regex allowlist - Update VSCode extension SUPPORTED_SCHEMA_VERSION from 1 to 5 - Update SECURITY.md supported versions to 2.0.x Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4343d59 commit 6355405

5 files changed

Lines changed: 21 additions & 15 deletions

File tree

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
| Version | Supported |
66
|---------|-----------|
7-
| 1.8.x | Yes |
8-
| < 1.8 | No |
7+
| 2.0.x | Yes |
8+
| < 2.0 | No |
99

1010
## Reporting a Vulnerability
1111

code-review-graph-vscode/src/backend/sqlite.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,8 @@ export class SqliteReader {
184184
.get() as { value: string } | undefined;
185185
if (row) {
186186
const version = parseInt(row.value, 10);
187-
// Current supported schema version
188-
const SUPPORTED_SCHEMA_VERSION = 1;
187+
// Must match LATEST_VERSION in code_review_graph/migrations.py
188+
const SUPPORTED_SCHEMA_VERSION = 5;
189189
if (!isNaN(version) && version > SUPPORTED_SCHEMA_VERSION) {
190190
return `Database was created with a newer version (schema v${version}). Update the extension.`;
191191
}

code_review_graph/changes.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
_GIT_TIMEOUT = int(os.environ.get("CRG_GIT_TIMEOUT", "30")) # seconds, configurable
2121

22+
_SAFE_GIT_REF = re.compile(r"^[A-Za-z0-9_.~^/@{}\-]+$")
23+
2224
# Security-sensitive keywords that increase a node's risk score.
2325
_SECURITY_KEYWORDS: set[str] = {
2426
"auth", "login", "password", "token", "session", "crypt", "secret",
@@ -47,12 +49,12 @@ def parse_git_diff_ranges(
4749
Mapping of file paths to lists of ``(start_line, end_line)`` tuples.
4850
Returns an empty dict on error.
4951
"""
50-
if base.startswith("-"):
51-
logger.warning("Invalid git ref (starts with '-'): %s", base)
52+
if not _SAFE_GIT_REF.match(base):
53+
logger.warning("Invalid git ref rejected: %s", base)
5254
return {}
5355
try:
5456
result = subprocess.run(
55-
["git", "diff", "--unified=0", "--", base],
57+
["git", "diff", "--unified=0", base, "--"],
5658
capture_output=True,
5759
text=True,
5860
cwd=repo_root,

code_review_graph/incremental.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import hashlib
1111
import logging
1212
import os
13+
import re
1314
import subprocess
1415
import time
1516
from pathlib import Path
@@ -130,15 +131,17 @@ def _is_binary(path: Path) -> bool:
130131

131132
_GIT_TIMEOUT = int(os.environ.get("CRG_GIT_TIMEOUT", "30")) # seconds, configurable
132133

134+
_SAFE_GIT_REF = re.compile(r"^[A-Za-z0-9_.~^/@{}\-]+$")
135+
133136

134137
def get_changed_files(repo_root: Path, base: str = "HEAD~1") -> list[str]:
135138
"""Get list of changed files via git diff."""
136-
if base.startswith("-"):
137-
logger.warning("Invalid git ref (starts with '-'): %s", base)
139+
if not _SAFE_GIT_REF.match(base):
140+
logger.warning("Invalid git ref rejected: %s", base)
138141
return []
139142
try:
140143
result = subprocess.run(
141-
["git", "diff", "--name-only", "--", base],
144+
["git", "diff", "--name-only", base, "--"],
142145
capture_output=True,
143146
text=True,
144147
cwd=str(repo_root),

code_review_graph/wiki.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,11 @@ def _generate_community_page(store: GraphStore, community: dict[str, Any]) -> st
103103
for flow in all_flows:
104104
# Check if this flow passes through any community member
105105
flow_members = store._conn.execute(
106-
"SELECT node_qualified_name FROM flow_memberships WHERE flow_id = ?",
106+
"SELECT n.qualified_name FROM flow_memberships fm "
107+
"JOIN nodes n ON fm.node_id = n.id WHERE fm.flow_id = ?",
107108
(flow["id"],),
108109
).fetchall()
109-
flow_qns = {r["node_qualified_name"] for r in flow_members}
110+
flow_qns = {r["qualified_name"] for r in flow_members}
110111
if flow_qns & member_set:
111112
community_flows.append(flow)
112113

@@ -273,9 +274,9 @@ def get_wiki_page(wiki_dir: str | Path, page_name: str) -> str | None:
273274
if filepath.is_file():
274275
return filepath.read_text(encoding="utf-8")
275276

276-
# Fallback: try exact filename match
277-
exact_path = wiki_path / page_name
278-
if exact_path.is_file():
277+
# Fallback: try exact filename match — with path traversal protection
278+
exact_path = (wiki_path / page_name).resolve()
279+
if exact_path.is_file() and exact_path.is_relative_to(wiki_path.resolve()):
279280
return exact_path.read_text(encoding="utf-8")
280281

281282
# Fallback: search for partial match

0 commit comments

Comments
 (0)