Skip to content

fix: address security vulnerability and task dependency inconsistency#1

Open
hobostay wants to merge 1 commit into
ivan-magda:masterfrom
hobostay:fix/security-and-consistency
Open

fix: address security vulnerability and task dependency inconsistency#1
hobostay wants to merge 1 commit into
ivan-magda:masterfrom
hobostay:fix/security-and-consistency

Conversation

@hobostay

Copy link
Copy Markdown

Summary

This PR fixes two issues in the codebase:

1. Security: Path Traversal Vulnerability in SkillLoader

Problem: Entry names from directory listing were not validated before being used in path construction. This could allow path traversal attacks if a directory entry contained special characters like ...

Impact: An attacker with ability to create directories in the skills directory could potentially read files outside the intended directory.

Fix: Added validation to reject entries containing:

  • Path separators (/ or \)
  • Dot prefixes (hidden files or parent directory references like . or ..)
// Before
let skillFile = "\(directory)/\(entry)/SKILL.md"

// After  
guard !entry.contains("/"),
      !entry.contains("\\"),
      !entry.hasPrefix(".")
else {
  continue
}

let skillFile = "\(directory)/\(entry)/SKILL.md"

2. Correctness: Asymmetric Task Dependencies in TaskManager

Problem: When adding blockedBy or blocks relationships, if the referenced task didn't exist, the relationship was only maintained one-way. This led to inconsistent state.

Example of the bug:

  • Task 1 sets addBlockedBy: [2]
  • Task 1 now has blockedBy: [2]
  • But Task 2 doesn't have blocks: [1] (because Task 2's file doesn't exist or failed to load)
  • The dependency graph is now inconsistent

Fix: Changed the behavior to:

  1. Throw taskNotFound error when the referenced task doesn't exist
  2. Revert any partial changes to maintain state consistency
  3. Only update the bidirectional relationship if both tasks exist

Testing

  • Project builds successfully with swift build
  • No new tests added as the existing test framework has dependency issues (requires Swift 6 Testing module)

Checklist

  • All changes build successfully
  • Code follows existing style conventions
  • Commit messages are clear and descriptive
  • Security fix properly validates input

🤖 Generated with Claude Code

This commit fixes two issues:

1. Security: Path traversal vulnerability in SkillLoader
   - Entry names were not validated before use in path construction
   - An entry like "../../../etc/passwd" could escape the skills directory
   - Added validation to reject entries with path separators or dot prefixes

2. Correctness: Asymmetric task dependencies in TaskManager
   - When adding blockedBy/blocks relationships, if the referenced task
     didn't exist, the relationship was only maintained one-way
   - This led to inconsistent state where task A claims to be blocked
     by task B, but task B doesn't list task A in its blocks array
   - Fixed by throwing taskNotFound error and reverting partial changes
     when the referenced task doesn't exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tombarr added a commit to LastByteLLC/junco that referenced this pull request Apr 24, 2026
EvalHarness was creating a fresh Orchestrator per case, forcing the index
building block (FileIndexer + ReferenceGraph + ProjectAnalyzer, ~3–5s each)
to run ~20 times for a full search-split eval.

Build one Orchestrator before the loop and pass it into runCase /
runDestructive. Orchestrator.run() already creates a fresh WorkingMemory
per call; the cross-case state is limited to project-wide artifacts
(index, snapshot, reference graph, embedding index) which SHOULD be
shared.

Baseline before: 161.1s wall, median 6.3s per case
Baseline after:   34.3s wall, median 0.1s per case  (−79% wall; −98% median)

Only case ivan-magda#1 pays the indexing cost (13.6s for search-build-target);
subsequent deterministic cases land at 0.0–0.3s. LLM-call cases
(plan-refactor, plan-new-mode, explain-spinner-file) unaffected since
they're dominated by AFM time, not setup.

Spot-check: distinct, relevant answers per case confirm fast cases
aren't returning cached garbage. 20/20 still pass. 590 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tombarr added a commit to LastByteLLC/junco that referenced this pull request Apr 24, 2026
… runs

Diagnosis: decomposing case ivan-magda#1's 13s answer-stage cost (for search queries
with 0 LLM calls) showed only ~1.2s was spent on FileIndexer + ReferenceGraph
+ ProjectAnalyzer. The remaining ~12s was the first `embeddingIndex.score()`
call blocking behind the "background" buildIndex task — actor serialization
made the detached build effectively synchronous for the first consumer.

Root cause: NLEmbedding.vector(for:) at ~2ms × 3745 entries = ~7.5s of cold
embedding compute, repeated every process start. The embedding texts ("{symbolName}
{first-line}") are stable across runs — ideal cache key.

Fix: EmbeddingIndex now persists a text→vector cache to
`.junco/embedding_cache.json`. On init, loads the cache into memory.
buildIndex checks the cache before calling NLEmbedding; misses compute once
and are persisted. addEntries handles incremental updates likewise.

Measured on `baseline --split search`:
  cold cache:  case ivan-magda#1 = 12.0s, total wall = 34s
  warm cache:  case ivan-magda#1 =  1.2s, total wall = 19s   (−44% on top of prior
                                                    Orchestrator-reuse)

Cumulative session progress:
  original:      wall 161s, median 6.3s per case
  post-reuse:    wall  34s, median 0.1s
  post-cache:    wall  19s, median 0.07s           (−88% from original)

The cache file is 32 MB JSON for this project (3745 entries × 512-dim doubles).
Candidate for compression / binary encoding later. 590 tests green, canary 4/4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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