1jehuang / 1jehuang/jcode

Full Audit - Bugs

Open
#135 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: low question triage: needs-decision
Dominant language
Rust
Stars
19.9k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
30

Description

jcode Codebase Audit

Project: jcode v0.11.4 — AI coding agent harness
Scale: ~344K lines Rust, 731 files


Summary

Severity Count
Critical 5
High 8
Medium 13
Low 11
Total 37

Critical

1. Unsound Unsafe Code — Undefined Behavior in Windows Transport

File: src/transport/windows.rs:120–128

pub fn split(&mut self) -> (SplitReadRef<'_>, SplitWriteRef<'_>) {
    let ptr = self as *mut Stream;
    unsafe {
        (
            SplitReadRef { stream: &mut *ptr },
            SplitWriteRef { stream: &mut *ptr },
        )
    }
}

Creates two &mut references to the same Stream — undefined behavior per Rust's aliasing rules. If both are used concurrently from different async tasks (which is the entire point), the compiler may misoptimize reads/writes. The codebase uses Arc<Mutex<Stream>> for into_split() elsewhere, but this split() path is unsound.


2. Session Save Failures Silently Discarded

Files: src/tui/app/turn.rs:275, 828, 895, 1165, 1178 + ~30 more across TUI

let _ = self.session.save() silently discards persistence failures. The most critical save (post-turn assistant message at line 828) is swallowed. If disk is full or permissions are wrong, the user loses their entire conversation with zero indication. Line 966 proves the pattern is known (self.session.save()?), making the other five in the same file oversights.


3. Nested std::sync::Mutex Locks in Async Context — Blocks Runtime

File: src/memory/pending.rs:85–123

take_pending_memory acquires three nested std::sync::Mutex locks (PENDING_MEMORYLAST_INJECTED_PROMPT_SIGNATURELAST_INJECTED_MEMORY_SET). These block the OS thread, stalling the tokio async runtime. No defined lock ordering protocol exists across the codebase.


4. Mutex<Agent> Held Across .await Points — Deadlock Risk

Files: src/server/client_state.rs:455–528, debug_command_exec.rs:147, debug_jobs.rs:125, swarm.rs:1004, 1019, 1062

The agent.lock().await guard is held through multiple .await calls (tool_names().await, compaction_mode().await) that acquire internal RwLocks. If any other code path acquires those RwLocks first then tries to lock the Agent, deadlock occurs.


5. 100+ Global Mutable Statics — Prevents Testing, Enables Races

Files: src/tui/, src/agent.rs, src/usage/, src/provider/

The TUI alone has 63 global statics (OnceLock<Mutex<...>>, Atomic*). The codebase has 100+ total. This makes unit testing nearly impossible (hence the 79 #[cfg(test)]/#[cfg(not(test))] alternations in ui.rs), prevents multi-instance usage, and creates subtle race conditions from stale state.


High

6. God Module: src/tui/ui.rs — 2,578 Lines + 29 #[path] Directives

The file aggregates 29 submodules via #[path = "..."] overrides, pulling in ~40,000+ total lines. 12 functions have their entire bodies duplicated for test vs. non-test. The update_prompt_entry_animation function has 130 lines of verbatim duplication.


7. Error Classification via String Matching — Fragile

Files: src/provider/failover.rs:206–273, src/auth/oauth.rs:1030, src/auth/cursor.rs:514

Retry and failover decisions are made by .contains() on lowercased error strings. "billing" matches any error mentioning billing. "quota" matches unrelated errors. If upstream providers change their error messages, failover behavior silently degrades. There are only 2 typed error enums in the entire 344K-line codebase.


8. Operator Precedence Bug in Retryable Error Check

File: src/provider/openrouter_sse_stream.rs:199–210

fn is_retryable_error(error_str: &str) -> bool {
    crate::provider::is_transient_transport_error(error_str)
        || error_str.contains("stream error")
        || error_str.contains("eof")
        || error_str.contains("5")           // binds with &&, not ||
            && (error_str.contains("50")
                || error_str.contains("502")
                || ...)
        || error_str.contains("overloaded")
}

&& binds tighter than ||, so the contains("5") check only fires conjunctively. Meanwhile contains("502") already implies contains("5"), making the entire clause redundant dead logic.


9. Five Divergent is_retryable_error Implementations

Files: anthropic.rs:1534, openai_stream_runtime.rs:1064, copilot.rs:979, claude.rs:1060, openrouter_sse_stream.rs:199

Each has different coverage. claude.rs is missing 500 Internal Server Error. openai_stream_runtime.rs doesn't delegate to the shared is_transient_transport_error, missing TLS/DNS errors. These should be one function.


10. Unbounded SSE Buffer Growth — Memory Leak

Files: src/provider/anthropic.rs:1466–1509, copilot.rs:834, openrouter_sse_stream.rs:512

The buffer string grows without bound. parse_sse_event only drains up to \n\n — if the server sends malformed data with no double-newline, the buffer grows forever. No size limit or compaction exists. Same pattern for ToolUseAccumulator.input_json which accumulates arbitrarily large tool inputs.


11. Function Takes 22 Parameters

File: src/server/client_lifecycle.rs:137

handle_lightweight_control_request takes 22 parameters. Its callers pass the same 22 arguments in deeply repetitive match arms. Needs a context struct.


12. 350-Line Command Dispatcher

File: src/tui/app/commands.rs:1151–1501

handle_session_command is a 350-line if/else chain handling ~30 slash commands with no separation of concerns.


13. Inconsistent Default Model Fallbacks

File: src/provider/mod.rs:1073–1110

MultiProvider::model() fallback for Claude is "claude-opus-4-5-20251101" but anthropic.rs DEFAULT_MODEL is "claude-opus-4-6". Copilot fallback is "claude-sonnet-4" but copilot.rs uses "claude-sonnet-4-6". These mismatches mean the fallback path produces a different model than the provider's own default.


Medium

14. Dead Duplicate Files (535 lines of dead code)
  • src/usage_display.rs (176 lines) is byte-identical to src/usage/display.rs
  • src/usage_openai.rs (359 lines) is byte-identical to src/usage/openai_helpers.rs
  • src/tui/color_support.rs is byte-identical to crates/jcode-tui-workspace/src/color_support.rs

15. 100+ Hardcoded rgb() Magic Numbers

Despite ui_theme.rs defining named color functions (user_color(), file_link_color()), raw rgb(138, 180, 248) literals are scattered across 10+ TUI files. If the theme changes, these won't update.


16. Full-Frame clear_area Defeats Differential Rendering

File: src/tui/ui.rs:1852

clear_area(frame, area);  // "needed for macOS" but applied on ALL platforms

Forces a full-screen redraw every frame, defeating ratatui's diff-based rendering on all platforms.


17. Double prepare_messages on Every Frame

File: src/tui/ui.rs:2121–2141

When content overflows, prepare_messages is called twice per frame (full width, then narrow width). This is O(n) over all messages in the session.


18. No Path Traversal Protection

File: src/tool/mod.rs:158–166

resolve_path joins relative paths to working_dir with no canonicalize or traversal check. ../../etc/passwd resolves without complaint.


19. No SSRF Protection in WebFetch

File: src/tool/webfetch.rs:70–73

URL validation checks for http:///https:// prefix but doesn't block internal network addresses (169.254.169.254, localhost, 127.0.0.1). Cloud metadata endpoints are accessible.


20. Unbounded Collections — Memory Growth
  • src/server/runtime.rs:36file_touches: HashMap<PathBuf, Vec<FileAccess>> has no eviction
  • src/server/runtime.rs:43event_history: VecDeque<SwarmEvent> grows forever
  • src/provider/openrouter.rs:140–156DISK_CACHE_MEMO grows monotonically
  • src/session.rs:132env_snapshots, memory_injections, replay_events unbounded within session

21. Copilot fork() Shares Mutable State Inconsistently

File: src/provider/copilot.rs:1128–1144

fork() shares bearer_token, fetched_models, premium_mode, user_turn_count via Arc. Anthropic's fork() creates fresh credentials. A forked Copilot provider modifying premium_mode affects the original.


22. std::sync::Mutex Does File I/O Under Lock
  • src/memory_log.rs:20, 43–48writeln! + flush while holding the lock
  • src/telemetry.rs:20 — constructs HTTP payloads while holding the lock

Both can block the async runtime thread.


23. Hardcoded Version Strings Masquerading as Claude Code CLI

File: src/provider/anthropic.rs:41–80

const CLAUDE_CLI_USER_AGENT: &str = "claude-cli/2.1.123 (external, sdk-cli)";
const OAUTH_BILLING_HEADER: &str = "cc_version=2.1.123; cc_entrypoint=sdk-cli; cch=33f85;";

Six different version strings scattered across constants with no single source of truth. The billing header hash cch=33f85 will silently become stale.


24. 8+ Re-implementations of EnvVarGuard Test Utility

An identical set/restore env var pattern is independently implemented in 8+ test files. Should be a shared utility.


25. Debug Socket Allows Arbitrary Tool Execution

File: src/server/debug_command_exec.rs:117–617

Any local process can connect and execute arbitrary tool calls, change providers, or send messages. Socket permissions are owner-only, but on a shared machine this is still a risk.


26. Missing Config Validation

File: crates/jcode-config-types/src/lib.rs

CompactionConfig f32 fields (ewma_alpha, proactive_floor, etc.) have no range validation. DisplayConfig::animation_fps is documented as "1–120" but unchecked. AmbientConfig doesn't validate min_interval_minutes < max_interval_minutes.


Low

27. unreachable!() in Wire Format Parser

File: src/provider/cursor.rs:722

External data with an unexpected wire type will panic instead of returning an error.


28. Provider Trait Has 37 Methods — Interface Segregation Violation

File: src/provider/mod.rs:66–360

Methods like premium_mode(), transport(), native_compaction_mode() are OpenAI/Copilot-specific but pollute the core trait.


29. Provider::model() Default Returns "unknown"

File: src/provider/mod.rs:102–104

A sentinel string used in logging and routing. Should be Option<String>.


30. No Tests in 10+ Type Crates

jcode-auth-types, jcode-ambient-types, jcode-background-types, jcode-batch-types, jcode-config-types, jcode-gateway-types, jcode-memory-types, jcode-message-types, jcode-side-panel-types, jcode-usage-types — all have zero tests.


31. 9 Workspace Crates Missing publish = false

Internal crates like jcode-agent-runtime, jcode-embedding, jcode-pdf, etc. could be accidentally published to crates.io.


32. 68 Flat Modules in src/lib.rs

No intermediate grouping. The root crate has 76 public modules and ~50 direct dependencies compiled as one unit.


33. Log Files Created Without Restrictive Permissions

File: src/logging.rs:156–160

Created with default umask (typically 0o644), but logs contain truncated tool inputs/outputs that could include sensitive data.


34. Dummy Waker in Windows Transport

File: src/transport/windows.rs:149–160

A no-op Waker that never wakes. Works for one-shot polling but is fragile.


35. Permission Recording Failures Silently Discarded

File: src/tui/permissions.rs:55, 70, 85, 96

If record_permission_via_file() fails, a user's approve/deny decision is lost with no indication.


36. Git Dependencies

agentgrep and mermaid-rs-renderer are pinned by git tag. Less reliable than registry dependencies.


37. Release Profile Uses opt-level = 1 and codegen-units = 256

File: Cargo.toml:210–214

Unusually low quality for a release build. A separate release-lto profile exists for distribution.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

This is a broad audit rather than a scoped change: start by selecting one finding and reading its cited entry point, such as src/transport/windows.rs, src/provider/openrouter_sse_stream.rs, or src/tool/webfetch.rs. Confirm the behavior with a focused reproduction or existing test, then narrow the issue to one verified bug with a regression test and clear completion criteria.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, cli, performance, security, testing-qa
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
15/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.