Skip to content

replay-snapshot.ts's normalizeCommitSha has no format validation, letting a crafted value escape the intended snapshot directory via path.join #7796

Description

@JSONbored

Context

normalizeCommitSha (packages/loopover-miner/lib/replay-snapshot.ts:78-90) accepts any non-empty string with no hex/format check, and the unvalidated value is joined directly into a filesystem path:

function normalizeCommitSha(commitSha: string): string {
  if (typeof commitSha !== "string" || !commitSha.trim()) throw new Error("invalid_commit_sha");
  return commitSha.trim();               // accepts ANY non-empty string, no hex/format check
}

export const REPLAY_SNAPSHOT_SUBDIR = ".loopover-replay-snapshots";

export function planReplaySnapshotPath(input: { repoPath: string; commitSha: string }): string {
  const commitSha = normalizeCommitSha(input.commitSha);
  return join(input.repoPath, REPLAY_SNAPSHOT_SUBDIR, commitSha);   // join() with unvalidated commitSha
}

This is the same class of bug repo-clone.ts's own comment warns about for owner/repo segments — except here it's commitSha, and unlike replay-task-generation.ts's sibling commit-SHA handling (/^[0-9a-f]{7,40}$/i, used at lines 122/171/192 of that file), replay-snapshot.ts never applies that same format check before using the value in a real path.join().

Verified empirically:

planReplaySnapshotPath({
  repoPath: "/home/miner/.config/loopover-miner/repos/acme/widgets",
  commitSha: "../../../../../../tmp/evil-worktree",
})
// => "/home/miner/tmp/evil-worktree"   (escapes repoPath entirely)

addDetachedWorktree would then run git worktree add --detach <that escaped path> <commitSha> — a crafted commitSha controls where on disk a git worktree gets created, outside the intended .loopover-replay-snapshots sandbox subdirectory.

Current reach: exportReplaySnapshot/planReplaySnapshotPath has zero production callers today (confirmed via repo-wide grep — only test/unit/miner-replay-snapshot.test.ts calls it; replay-task-bridge.ts's own header comment confirms "nothing calls exportReplaySnapshot yet either"). This is a latent, well-scoped correctness/hardening gap in code that's about to gain a real caller as the replay/calibration feature is wired up — not a currently-live vulnerability, since nothing reachable from a live CLI path calls it yet. The existing test suite never passes a malformed/traversal-shaped commitSha — every case uses simple values like "abc123".

Requirements

Reuse replay-task-generation.ts's existing /^[0-9a-f]{7,40}$/i commit-SHA validator (or an equivalent format check) in normalizeCommitSha, so a non-hex or path-traversal-shaped commitSha is rejected before it ever reaches path.join().

Deliverables

  • normalizeCommitSha in packages/loopover-miner/lib/replay-snapshot.ts rejects any value that isn't a valid commit-SHA-shaped hex string, matching replay-task-generation.ts's existing validator.
  • A regression test covering a path-traversal-shaped commitSha (e.g. containing ../), asserting planReplaySnapshotPath/normalizeCommitSha rejects it instead of producing an escaped path.

Test Coverage Requirements

This file is under src/ via the packages/loopover-miner workspace - confirm current coverage.include scoping before assuming the top-level 99% patch gate applies exactly as src/** does; the new guard branch and its test must both be covered.

Expected Outcome

A malformed or path-traversal-shaped commitSha passed to the replay-snapshot path planner is rejected outright, instead of silently producing a path outside the intended .loopover-replay-snapshots sandbox directory - closing this gap before the in-progress replay/calibration feature gains a live caller.

Links & Resources

packages/loopover-miner/lib/replay-snapshot.ts:78-90, packages/loopover-miner/lib/replay-task-generation.ts:122,171,192 (the existing commit-SHA validator to reuse), packages/loopover-miner/lib/repo-clone.ts (the analogous owner/repo path-safety concern this mirrors)

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions