Skip to content

Commit 993d088

Browse files
haasonsaasclaude
andcommitted
Fix bugs found during code audit
Server fixes: - Remove unwrap() on reqwest client builder in doctor handler (panic risk) - Use saturating_mul for pagination to prevent integer overflow on large page values - Cap page/per_page to sane maximums (10000/100) - Validate branch names against allowed characters to prevent injection - Return 404 for unmatched /api/ routes instead of SPA HTML fallback - Use tokio::fs instead of blocking std::fs in async save task - Log persistence errors to stderr instead of silently ignoring Helm chart fixes: - Fix GPU resources creating duplicate 'limits' key in ollama-deployment (now properly merges nvidia.com/gpu into existing limits block) - Move PVC YAML separator inside conditional to avoid dangling '---' when first PVC is not rendered Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ed06fa4 commit 993d088

File tree

5 files changed

+51
-16
lines changed

5 files changed

+51
-16
lines changed

charts/diffscope/templates/ollama-deployment.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,17 @@ spec:
5252
port: http
5353
initialDelaySeconds: 10
5454
periodSeconds: 10
55+
{{- if .Values.ollama.gpu.enabled }}
5556
resources:
56-
{{- toYaml .Values.ollama.resources | nindent 12 }}
57-
{{- if .Values.ollama.gpu.enabled }}
57+
requests:
58+
{{- toYaml .Values.ollama.resources.requests | nindent 14 }}
5859
limits:
60+
{{- toYaml .Values.ollama.resources.limits | nindent 14 }}
5961
nvidia.com/gpu: {{ .Values.ollama.gpu.count }}
60-
{{- end }}
62+
{{- else }}
63+
resources:
64+
{{- toYaml .Values.ollama.resources | nindent 12 }}
65+
{{- end }}
6166
volumeMounts:
6267
- name: ollama-data
6368
mountPath: /root/.ollama

charts/diffscope/templates/pvc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ spec:
1919
requests:
2020
storage: {{ .Values.persistence.size }}
2121
{{- end }}
22-
---
2322
{{- if and .Values.ollama.enabled .Values.ollama.persistence.enabled (not .Values.ollama.persistence.existingClaim) }}
23+
---
2424
apiVersion: v1
2525
kind: PersistentVolumeClaim
2626
metadata:

src/server/api.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ async fn run_review_task(
145145
let config = state.config.read().await.clone();
146146
let repo_path = state.repo_path.clone();
147147

148+
// Validate branch name to prevent injection
149+
if let Some(ref branch) = base_branch {
150+
if !branch.chars().all(|c| c.is_alphanumeric() || matches!(c, '/' | '-' | '_' | '.')) {
151+
let mut reviews = state.reviews.write().await;
152+
if let Some(session) = reviews.get_mut(&review_id) {
153+
session.status = ReviewStatus::Failed;
154+
session.error = Some("Invalid branch name".to_string());
155+
session.completed_at = Some(current_timestamp());
156+
}
157+
return;
158+
}
159+
}
160+
148161
// Get the diff content based on source
149162
let diff_result = match diff_source.as_str() {
150163
"staged" => get_diff_from_git(&repo_path, "staged", None),
@@ -301,11 +314,12 @@ pub async fn list_reviews(
301314
list.sort_by(|a, b| b.started_at.cmp(&a.started_at));
302315

303316
// Apply pagination
304-
let page = params.page.unwrap_or(1).max(1);
305-
let per_page = params.per_page.unwrap_or(20).max(1);
306-
let start = (page - 1) * per_page;
317+
let page = params.page.unwrap_or(1).max(1).min(10_000);
318+
let per_page = params.per_page.unwrap_or(20).max(1).min(100);
319+
let start = (page - 1).saturating_mul(per_page);
307320
let list = if start < list.len() {
308-
list[start..list.len().min(start + per_page)].to_vec()
321+
let end = list.len().min(start.saturating_add(per_page));
322+
list[start..end].to_vec()
309323
} else {
310324
Vec::new()
311325
};
@@ -344,11 +358,6 @@ pub async fn get_doctor(State(state): State<Arc<AppState>>) -> Json<serde_json::
344358
.clone()
345359
.unwrap_or_else(|| "http://localhost:11434".to_string());
346360

347-
let client = reqwest::Client::builder()
348-
.timeout(std::time::Duration::from_secs(5))
349-
.build()
350-
.unwrap();
351-
352361
let mut result = serde_json::json!({
353362
"config": {
354363
"model": config.model,
@@ -363,6 +372,14 @@ pub async fn get_doctor(State(state): State<Arc<AppState>>) -> Json<serde_json::
363372
"recommended_model": null,
364373
});
365374

375+
let client = match reqwest::Client::builder()
376+
.timeout(std::time::Duration::from_secs(5))
377+
.build()
378+
{
379+
Ok(c) => c,
380+
Err(_) => return Json(result),
381+
};
382+
366383
// Check Ollama
367384
let ollama_url = format!("{}/api/tags", base_url);
368385
if let Ok(resp) = client.get(&ollama_url).send().await {

src/server/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ struct WebAssets;
2121
async fn serve_embedded(uri: axum::http::Uri) -> Response {
2222
let path = uri.path().trim_start_matches('/');
2323

24+
// Don't serve SPA for unmatched /api/ routes
25+
if path.starts_with("api/") {
26+
return (StatusCode::NOT_FOUND, "Not found").into_response();
27+
}
28+
2429
// Try exact path first, then fall back to index.html (SPA routing)
2530
let (file, serve_path) = if path.is_empty() {
2631
(WebAssets::get("index.html"), "index.html")

src/server/state.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,18 @@ impl AppState {
9393
drop(reviews);
9494

9595
if let Some(parent) = state.storage_path.parent() {
96-
let _ = std::fs::create_dir_all(parent);
96+
if let Err(e) = tokio::fs::create_dir_all(parent).await {
97+
eprintln!("Failed to create storage directory: {}", e);
98+
return;
99+
}
97100
}
98-
if let Ok(json) = serde_json::to_string_pretty(&stripped) {
99-
let _ = std::fs::write(&state.storage_path, json);
101+
match serde_json::to_string_pretty(&stripped) {
102+
Ok(json) => {
103+
if let Err(e) = tokio::fs::write(&state.storage_path, json).await {
104+
eprintln!("Failed to persist reviews: {}", e);
105+
}
106+
}
107+
Err(e) => eprintln!("Failed to serialize reviews: {}", e),
100108
}
101109
});
102110
}

0 commit comments

Comments
 (0)