Skip to content

fix(scope): validate remote lane scope/name to prevent path-traversal write#10414

Open
davidfirst wants to merge 8 commits into
masterfrom
fix/remote-lane-path-traversal
Open

fix(scope): validate remote lane scope/name to prevent path-traversal write#10414
davidfirst wants to merge 8 commits into
masterfrom
fix/remote-lane-path-traversal

Conversation

@davidfirst

Copy link
Copy Markdown
Member

A remote lane's scope/name come from a Lane object served by a remote scope — untrusted input. They were used unsanitized as path segments in composeRemoteLanePath (path.join(basePath, scope, name)), so a malicious/compromised remote (or a MITM on an http:// remote) could set them to ../../… and make bit switch <remote>/<lane> / bit lane import / bit fetch --lanes write a file outside the scope's refs directory via fs.outputFile.

The fix rejects any lane scope/name that isn't safe as a single path segment (contains /, \, .., NUL, or is absolute), enforced both at the untrusted boundary (syncWithLaneObject) and at the path choke point (composeRemoteLanePath).

Same class as the isValidPath fix (#10354) that closed the version-file write path; this completes coverage for the lane sink. Adds remote-lanes.spec.ts covering scope/name traversal, absolute, and backslash rejection plus a valid-name negative case.

… write

A remote lane's scope/name come from a Lane object served by a remote scope
(untrusted). They were used unsanitized as path segments in composeRemoteLanePath
(path.join), so a malicious/compromised remote could set scope/name to '../../...'
and make 'bit switch'/'bit lane import'/'bit fetch --lanes' write a file outside the
scope's refs directory via fs.outputFile.

Reject any lane scope/name that isn't safe as a single path segment (contains a
separator, '..', NUL, or is absolute) at the untrusted boundary (syncWithLaneObject)
and at the path choke point (composeRemoteLanePath). Same class as the isValidPath
fix that closed the version-file write path; this completes coverage for the lane sink.
Copilot AI review requested due to automatic review settings June 10, 2026 12:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens remote-lane ref persistence against path-traversal by validating untrusted Lane-originated scope/name values before they’re used as filesystem path segments, preventing a malicious remote (or MITM on HTTP) from writing outside the refs directory.

Changes:

  • Add assertValidRemoteLaneSegment() and enforce it at both the untrusted boundary (syncWithLaneObject) and the sink (composeRemoteLanePath).
  • Throw a BitError when a remote lane scope/name contains path separators, .., NUL, or is absolute.
  • Add unit coverage for traversal/absolute/backslash rejection and for a valid “does not throw” case.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
components/legacy/scope/lanes/remote-lanes.ts Adds runtime validation for remote lane scope/name before composing/writing remote-lane refs paths.
components/legacy/scope/lanes/remote-lanes.spec.ts Adds tests to ensure traversal/absolute/backslash inputs are rejected and valid identifiers pass.

Comment thread components/legacy/scope/lanes/remote-lanes.spec.ts Outdated
Comment thread components/legacy/scope/lanes/remote-lanes.ts Outdated
Replace the hand-rolled traversal predicate with the shared isValidPath helper
(@teambit/legacy.utils), matching Scope.getPendingDirPath. It is strictly stronger
(also rejects '.' segments, INVALID_CHARS, over-length, leading './') and removes a
duplicated bespoke variant of the same check. Also dedupe the spec's repeated
try/catch boilerplate into a grabError helper.
Copilot AI review requested due to automatic review settings June 10, 2026 13:04
…temp dirs

Address PR review: type assertValidRemoteLaneSegment's value as unknown (it defends
JSON-deserialized remote input that isn't guaranteed to be a string) and stringify
safely in the error. Empty/'.'/non-string are already rejected via isValidPath. Spec
now uses a unique mkdtemp dir + fresh instance per test with afterEach cleanup.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread components/legacy/scope/lanes/remote-lanes.ts
Comment thread components/legacy/scope/lanes/remote-lanes.spec.ts
…error

Use JSON.stringify(value) instead of String(value) so an invalid segment containing
control chars (e.g. newlines) can't produce multi-line/confusing CLI output. Matches
Scope.getPendingDirPath. Addresses PR review.
Copilot AI review requested due to automatic review settings June 10, 2026 13:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@davidfirst

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0)

Grey Divider


Remediation recommended

1. Unbounded error message input 🐞 Bug ☼ Reliability
Description
assertValidRemoteLaneSegment interpolates JSON.stringify(value) from an untrusted remote lane
into a BitError message; a malicious remote can send a very large scope/lane string and cause
large allocations and log/console flooding when the validation rejects it. This is an error-path
resource amplification risk introduced by the new guard.
Code

components/legacy/scope/lanes/remote-lanes.ts[R28-33]

+function assertValidRemoteLaneSegment(value: unknown, kind: 'lane scope' | 'lane name'): void {
+  if (typeof value !== 'string' || !isValidPath(value) || value.includes('/')) {
+    throw new BitError(
+      `invalid ${kind} ${JSON.stringify(value)} received from a remote; refusing to use it as a remote-lane ref path outside the scope directory`
+    );
+  }
Evidence
The thrown error message currently includes the entire remote-provided value; remote lane objects
are fetched from remotes and passed into syncWithLaneObject, so an attacker controlling the remote
response can choose an arbitrarily large string that will be rejected by isValidPath but still
fully stringified into the error message.

components/legacy/scope/lanes/remote-lanes.ts[18-34]
components/legacy/scope/component-ops/scope-components-importer.ts[519-528]
components/legacy/utils/is-valid-path.ts[22-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`assertValidRemoteLaneSegment()` throws a `BitError` that includes `JSON.stringify(value)` for remote-provided lane scope/name. If a remote sends a huge string, the rejection path builds/prints a huge error message, amplifying memory use and potentially flooding logs.
### Issue Context
This guard is called from `syncWithLaneObject()` for lane objects fetched from remotes.
### Fix Focus Areas
- components/legacy/scope/lanes/remote-lanes.ts[28-33]
### Suggested fix
- Introduce a small helper (e.g. `formatUntrusted(value: unknown)`) that:
- stringifies safely,
- truncates to a reasonable max (e.g. 200 chars),
- appends metadata like `"… (len=12345)"` when truncated.
- Use the truncated/annotated representation in the `BitError` message instead of stringifying the full value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Misleading guard error text ✓ Resolved 🐞 Bug ◔ Observability
Description
assertValidRemoteLaneSegment always throws an error that says “refusing to write…”, but the same
guard is also invoked from composeRemoteLanePath during read flows (loadRemoteLane), so the message
can be inaccurate and confusing when diagnosing failures.
Code

components/legacy/scope/lanes/remote-lanes.ts[R28-33]

+function assertValidRemoteLaneSegment(value: unknown, kind: 'lane scope' | 'lane name'): void {
+  if (typeof value !== 'string' || !isValidPath(value) || value.includes('/')) {
+    throw new BitError(
+      `invalid ${kind} ${JSON.stringify(value)} received from a remote; refusing to write the remote-lane ref outside the scope directory`
+    );
+  }
Evidence
The thrown message explicitly mentions writing, while composeRemoteLanePath (which calls the
assertion) is used by loadRemoteLane before reading JSON from disk, so the same message can be
emitted on read paths.

components/legacy/scope/lanes/remote-lanes.ts[28-34]
components/legacy/scope/lanes/remote-lanes.ts[132-150]
components/legacy/scope/lanes/remote-lanes.ts[187-191]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`assertValidRemoteLaneSegment()` throws a `BitError` message that says it is "refusing to write" the remote-lane ref, but this validator is also used when composing paths for reads (e.g. `loadRemoteLane()` -> `composeRemoteLanePath()` -> `fs.readJson`). This makes the error message misleading and harder to triage.
### Issue Context
The validation is correct; only the message text is inaccurate for some call sites.
### Fix Focus Areas
- components/legacy/scope/lanes/remote-lanes.ts[28-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit e1bc2f0

Results up to commit N/A


🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Remediation recommended
1. Unbounded error message input 🐞 Bug ☼ Reliability
Description
assertValidRemoteLaneSegment interpolates JSON.stringify(value) from an untrusted remote lane
into a BitError message; a malicious remote can send a very large scope/lane string and cause
large allocations and log/console flooding when the validation rejects it. This is an error-path
resource amplification risk introduced by the new guard.
Code

components/legacy/scope/lanes/remote-lanes.ts[R28-33]

+function assertValidRemoteLaneSegment(value: unknown, kind: 'lane scope' | 'lane name'): void {
+  if (typeof value !== 'string' || !isValidPath(value) || value.includes('/')) {
+    throw new BitError(
+      `invalid ${kind} ${JSON.stringify(value)} received from a remote; refusing to use it as a remote-lane ref path outside the scope directory`
+    );
+  }
Evidence
The thrown error message currently includes the entire remote-provided value; remote lane objects
are fetched from remotes and passed into syncWithLaneObject, so an attacker controlling the remote
response can choose an arbitrarily large string that will be rejected by isValidPath but still
fully stringified into the error message.

components/legacy/scope/lanes/remote-lanes.ts[18-34]
components/legacy/scope/component-ops/scope-components-importer.ts[519-528]
components/legacy/utils/is-valid-path.ts[22-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`assertValidRemoteLaneSegment()` throws a `BitError` that includes `JSON.stringify(value)` for remote-provided lane scope/name. If a remote sends a huge string, the rejection path builds/prints a huge error message, amplifying memory use and potentially flooding logs.
### Issue Context
This guard is called from `syncWithLaneObject()` for lane objects fetched from remotes.
### Fix Focus Areas
- components/legacy/scope/lanes/remote-lanes.ts[28-33]
### Suggested fix
- Introduce a small helper (e.g. `formatUntrusted(value: unknown)`) that:
- stringifies safely,
- truncates to a reasonable max (e.g. 200 chars),
- appends metadata like `"… (len=12345)"` when truncated.
- Use the truncated/annotated representation in the `BitError` message instead of stringifying the full value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational
2. Misleading guard error text ✓ Resolved 🐞 Bug ◔ Observability
Description
assertValidRemoteLaneSegment always throws an error that says “refusing to write…”, but the same
guard is also invoked from composeRemoteLanePath during read flows (loadRemoteLane), so the message
can be inaccurate and confusing when diagnosing failures.
Code

components/legacy/scope/lanes/remote-lanes.ts[R28-33]

+function assertValidRemoteLaneSegment(value: unknown, kind: 'lane scope' | 'lane name'): void {
+  if (typeof value !== 'string' || !isValidPath(value) || value.includes('/')) {
+    throw new BitError(
+      `invalid ${kind} ${JSON.stringify(value)} received from a remote; refusing to write the remote-lane ref outside the scope directory`
+    );
+  }
Evidence
The thrown message explicitly mentions writing, while composeRemoteLanePath (which calls the
assertion) is used by loadRemoteLane before reading JSON from disk, so the same message can be
emitted on read paths.

components/legacy/scope/lanes/remote-lanes.ts[28-34]
components/legacy/scope/lanes/remote-lanes.ts[132-150]
components/legacy/scope/lanes/remote-lanes.ts[187-191]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`assertValidRemoteLaneSegment()` throws a `BitError` message that says it is "refusing to write" the remote-lane ref, but this validator is also used when composing paths for reads (e.g. `loadRemoteLane()` -> `composeRemoteLanePath()` -> `fs.readJson`). This makes the error message misleading and harder to triage.
### Issue Context
The validation is correct; only the message text is inaccurate for some call sites.
### Fix Focus Areas
- components/legacy/scope/lanes/remote-lanes.ts[28-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

The guard is also reached via composeRemoteLanePath on read flows (loadRemoteLane),
so 'refusing to write' was misleading there. Reword to 'refusing to use it as a
remote-lane ref path'. Message-only; addresses automated review feedback.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 59c96ca

@davidfirst

Copy link
Copy Markdown
Member Author

Re: Qodo's "Misleading guard error text" finding — addressed in 59c96ca. The guard is indeed reached from composeRemoteLanePath on read flows too (loadRemoteLane), so the message no longer says "refusing to write"; it now reads "…refusing to use it as a remote-lane ref path outside the scope directory", which is accurate for both read and write paths. Message-only change; the invalid lane scope/invalid lane name prefix (and the tests asserting it) are unchanged.

@davidfirst davidfirst enabled auto-merge (squash) June 10, 2026 20:55
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e1bc2f0

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.

3 participants