fix(deps): allow same-repo remote path deps#1732
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cal-deps-shared-repo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Enables a narrowly-scoped behavior change in the dependency resolver: remote (git-cloned) packages may now use relative path: dependencies to reference sibling packages within the same remote repository, while continuing to reject absolute paths, repo-escape traversal, and cross-repo paths.
Changes:
- Add resolver logic to expand eligible remote-parent relative
path:dependencies into same-origin remote virtual dependencies. - Add regression tests covering the allowed same-repo sibling case and key fail-closed rejection cases.
- Update inline docs/comments and user-facing docs to describe the new same-repo-only boundary, plus a changelog entry under
[Unreleased].
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/deps/apm_resolver.py |
Implements same-repo expansion + centralized rejection reporting for remote-parent path: deps. |
tests/test_apm_resolver.py |
Adds coverage for sibling expansion and fail-closed path rejection scenarios. |
src/apm_cli/install/phases/local_content.py |
Updates docstring commentary to reflect resolver-level expansion/rejection boundary. |
src/apm_cli/deps/path_anchoring.py |
Updates module-level commentary on the untrusted-source boundary (remote-parent path handling). |
packages/apm-guide/.apm/skills/apm-usage/dependencies.md |
Documents same-repo-only semantics for remote-declared relative path: deps. |
docs/src/content/docs/reference/manifest-schema.md |
Documents remote path: scoping to the same repo root and rejection cases. |
docs/src/content/docs/consumer/manage-dependencies.md |
Adds an example + narrative describing remote monorepo sibling path: usage and constraints. |
CHANGELOG.md |
Adds an [Unreleased] fixed entry for #1571 behavior change. |
Copilot's findings
- Files reviewed: 8/8 changed files
- Comments generated: 2
| local_path = Path(local_str.replace("\\", "/")) | ||
| resolved = ensure_path_within(parent_source / local_path, repo_root) | ||
| virtual_path = portable_relpath(resolved, repo_root) | ||
| if virtual_path in ("", "."): |
| ### Fixed | ||
|
|
||
| - `apm install` now resolves relative `path:` deps declared by remote monorepo packages when they stay inside the same remote repo, while still rejecting absolute, escaping, or cross-repo paths. (closes #1571) |
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 1 | 1 | Clean extraction; repo-root derivation is coupled to install-path depth. |
| CLI Logging Expert | 0 | 0 | 1 | Rejection message is actionable; one run-on punctuation nit. |
| DevX UX Expert | 0 | 1 | 0 | Fallback rejection wording is more jargon-y than the expansion-path messages. |
| Supply Chain Security Expert | 0 | 1 | 0 | Containment fence is sound and fail-closed; add a real-FS symlink-escape proof. |
| OSS Growth Hacker | 0 | 0 | 0 | Narrow, well-fenced monorepo unlock worth amplifying. |
| Test Coverage Expert | 0 | 1 | 0 | Same-repo/escape/cross-repo covered; absolute, registry, and fallback branches are not. |
| Auth Expert | 0 | 1 | 0 | Same-origin reuse is correct; success test mocks the downloader, so reuse is unproven. |
| Doc Writer | 0 | 0 | 1 | Docs + skill resource + CHANGELOG all updated; consider a security-model line. |
| Performance Expert | 0 | 0 | 0 | Clone reuse avoids a second fetch; path math is negligible. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 5 follow-ups
- [Test Coverage Expert] Add rejection tests for the absolute-path, registry-parent, and fallback-loader branches. -- Verified on the head ref that only 3 tests exist (same-repo success, escape, cross-repo); these new security branches have no asserting test, so a silent drift would not fail loudly.
- [Supply Chain Security Expert] Add a real-filesystem test that a symlink inside the clone pointing outside the repo root is rejected. -- The anti-exfiltration guard is load-bearing and shared with [FEATURE] fetch path from gitlab repository using git clone instead of API #1014/feat(deps): fetch path-scoped files over git transport instead of host API #1740; current unit tests mock the downloader and exercise only pure-path cases.
- [Auth Expert] Add an integration assertion that the sibling fetch reuses the parent's per-dep auth context (same host/token). -- The success test runs against a mocked downloader; an integration proof guards against a future change silently routing the sibling through a different origin or credential.
- [Python Architect] Document or guard the coupling between
virtual_pathsegment depth and repo-root derivation in_remote_repo_root_for_parent. -- Containment correctness depends onvirtual_pathmirroring the on-disk depth; an install-layout change must update this in lockstep or the fence could miscompute. - [DevX UX Expert] Align the fallback loader rejection wording with the clearer expansion-path messages. -- Users who hit the fallback see "could not be tied to its authenticated repo root" instead of the actionable phrasing used elsewhere.
Architecture
classDiagram
class APMDependencyResolver {
-Path _apm_modules_dir
-set _rejected_remote_local_keys
+build_dependency_tree(root_apm_yml) DependencyTree
+expand_parent_repo_decl(parent_dep, child_dep) DependencyReference
-_inherit_remote_parent_fields(parent_dep, child_dep) DependencyReference
-_is_absolute_local_path(local_path) bool
-_remote_repo_root_for_parent(parent_dep, parent_pkg) Path
-_expand_remote_parent_local_path(parent_dep, parent_pkg, child_dep) DependencyReference
-_reject_remote_parent_local_path(dep_ref, parent_pkg, detail) void
-_expand_or_reject_remote_parent_local_path(parent_dep, parent_pkg, child_dep) DependencyReference
-_try_load_dependency_package(dep_ref, parent_pkg) APMPackage
}
class DependencyReference {
+str repo_url
+str virtual_path
+str reference
+bool is_local
+str local_path
+bool is_virtual
}
class PathSecurity {
+ensure_path_within(path, base) Path
+validate_path_segments(path_str, context) void
}
APMDependencyResolver ..> DependencyReference : produces
APMDependencyResolver ..> PathSecurity : enforces containment
flowchart TD
A[BFS expands sub_dep] --> B{is_local and remote parent?}
B -- no --> Z[Use child_dep unchanged]
B -- yes --> C{registry parent or absolute path?}
C -- yes --> R[Reject: record key and emit _rich_error]
C -- no --> D[Compute repo_root from source_path and virtual_path]
D --> E[ensure_path_within parent_source/local_path inside repo_root]
E -- escapes root --> R
E -- inside root --> F[Expand to same-origin virtual dep]
F --> G[Inherit host/repo/ref, registry_name None]
G --> H[Downloader reuses existing clone]
R --> I[Fallback loader reject stays fail-closed]
Recommendation
Ship now. There are no blocking-severity findings: the same-repo containment boundary is implemented through the sanctioned guards, fails closed, and reuses the parent's authenticated origin. The highest-signal follow-up to track is the test-coverage gap -- the new absolute-path, registry-parent, and fallback-loader rejection branches (plus a real-FS symlink-escape case and an auth-reuse integration assertion) should get targeted tests so this security surface, which #1740 builds on, cannot drift silently.
Full per-persona findings
Python Architect
- [recommended] Repo-root derivation is coupled to the install-path layout at
src/apm_cli/deps/apm_resolver.py
_remote_repo_root_for_parentwalks up one parent pervirtual_pathsegment to find the clone root, then containment is checked against it. This is correct for the current layout, but the security boundary's correctness silently depends onvirtual_pathfaithfully mirroring the on-disk depth ofsource_path. If the install path layout ever changes, this derivation must change in lockstep or containment could miscompute the root.
Suggested: Add a short invariant comment (or an assertion thatsource_pathends withvirtual_path) so the coupling is explicit and guarded. - [nit] The
_inherit_remote_parent_fieldsextraction is a clean de-duplication of the field-inheritance block and keeps the expansion vs fallback-reject split readable; a one-line module comment naming those two tiers would help future readers.
CLI Logging Expert
- [nit] Run-on rejection message at
src/apm_cli/deps/apm_resolver.py
The composed error is"{detail} Use a relative path...", but somedetailstrings have no terminal punctuation (e.g. "absolute paths inside remote packages are not allowed"), producing "...not allowed Use a relative path...". Normalize detail strings to end with a period. The message otherwise names the dependency, the package, the reason, and the fix -- good.
DevX UX Expert
- [recommended] Inconsistent rejection wording across the two reject paths at
src/apm_cli/deps/apm_resolver.py
The expansion-pathPathTraversalErrormessages are specific and user-readable, but the fallback loader passes "remote package path could not be tied to its authenticated repo root." which is internal jargon. A user who lands on the fallback gets a less actionable message than one who hits the expansion guard.
Suggested: Reuse the same "stays inside the same remote repo / publish as a standalone package" phrasing for both paths.
Supply Chain Security Expert
- [recommended] Containment fence is sound; add a real-filesystem escape proof.
src/apm_cli/deps/apm_resolver.py
The boundary correctly routes throughensure_path_within(symlink-resolving, fail-closed) andvalidate_path_segments, rejects registry and absolute parents, and reuses the parent origin withregistry_name=None-- no new credential or origin is introduced, which is the right anti-exfiltration posture. The gap is proof: the existing tests mock the download callback and exercise only string-path cases, so a symlink inside the clone that points outside the repo root is not exercised end-to-end. Given this surface is load-bearing for [FEATURE] fetch path from gitlab repository using git clone instead of API #1014/feat(deps): fetch path-scoped files over git transport instead of host API #1740, a real-FS symlink-escape regression test would lock the guarantee.
OSS Growth Hacker
No findings.
Test Coverage Expert
- [recommended] New rejection branches lack asserting tests.
tests/test_apm_resolver.py
Verified against the head ref (danielmeppiel/1571-local-deps-shared-repo): the diff adds exactly threetest_remote_parent_*tests -- same-repo sibling success, repo-root escape, and cross-repo clone -- which cover the primary boundary well. But the PR also adds an absolute-path rejection branch (_is_absolute_local_path, POSIX + Windows), a registry-parent rejection branch, and the fallback_try_load_dependency_packagereject path; none of these has a test that would fail if it silently regressed.
Suggested: Add targeted rejection tests for an absolutepath:(e.g./etc/xand a WindowsC:\\x), a registry-parent declaring apath:, and a dep that reaches the fallback loader.
Auth Expert
- [recommended] Same-origin reuse is correct but unproven by the success test.
src/apm_cli/deps/apm_resolver.py
Expansion inherits the parent'srepo_url/host/ref and resetsregistry_name=None, so the sibling is fetched under the same authenticated origin and shared clone with no second credential resolution -- the safe design that avoids origin confusion. However, the success test assertsrepo_url/ref/virtual_pathagainst a mocked downloader, so it does not prove the real per-dep auth context is reused for the sibling fetch.
Suggested: Add an integration-level assertion that the sibling resolves through the same host/token as the parent, so a future change cannot silently route it through a different credential.
Doc Writer
- [nit] Docs are consistent and Rule 4 is satisfied:
docs/src/content/docs/consumer/manage-dependencies.md,docs/src/content/docs/reference/manifest-schema.md, thepackages/apm-guide/.apm/skills/apm-usage/dependencies.mdskill resource, andCHANGELOG.mdall describe the same-repo-only boundary. Consider adding one line to the security model doc (docs/src/content/docs/enterprise/security.md) so the path-safety contract mirrors the new expansion/rejection behavior.
Performance Expert
No findings.
This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.
…t API For GitLab-sourced deps (SaaS or self-hosted), path: file fetches now go through git sparse/partial checkout (--filter=blob:none + cone sparse-checkout) rather than the host REST API. This is the primary path and fixes self-hosted GitLab instances that return HTTP 410 (REST API disabled), because git transport reuses the same SSH keys and git credential helpers already in use for clones. Design (ratified by board decision): - git-transport-first: fetch_file_via_git_sparse() in the new deps/git_file_transport.py module performs git init + remote add + sparse-checkout init --cone + fetch --filter=blob:none --depth=1 + checkout - GITLAB_APM_PAT fallback: if git transport raises, DownloadDelegate.download_gitlab_file() falls back to the existing GitLab REST v4 API (mirrors ADO_APM_PAT pattern) - Path containment: ensure_path_within() is applied to the materialized file path after checkout, rejecting symlink/traversal escapes from cloned repos - validate_path_segments() rejects .. traversal sequences before any git work - Reuses the existing clone auth environment (git_env from the downloader); no second origin or cached clone TDD coverage (tests/test_gitlab_git_transport.py): - Acceptance: git-sourced dep with path: fetches file via git (no REST API call) - Self-hosted GitLab API-410 no longer fails - ensure_path_within() rejects symlink-escaping path: after checkout - validate_path_segments() rejects .. traversal before git work - GITLAB_PAT fallback path exercised when git transport raises - Root-level files skip cone setup (no sparse-checkout init) - git failure raises RuntimeError with descriptive message - File missing after checkout raises RuntimeError Docs: updated consumer/authentication.md, consumer/manage-dependencies.md, apm-usage/authentication.md, apm-usage/dependencies.md, and CHANGELOG.md. Stacked on #1732 (#1571). Closes #1014. Credit: @cossons for root-cause analysis of the self-hosted GitLab 410 repro and the ratified design (git-transport-first + thin PAT fallback). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t API For GitLab-sourced deps (SaaS or self-hosted), path: file fetches now go through git sparse/partial checkout (--filter=blob:none + cone sparse-checkout) rather than the host REST API. This is the primary path and fixes self-hosted GitLab instances that return HTTP 410 (REST API disabled), because git transport reuses the same SSH keys and git credential helpers already in use for clones. Design (ratified by board decision): - git-transport-first: fetch_file_via_git_sparse() in the new deps/git_file_transport.py module performs git init + remote add + sparse-checkout init --cone + fetch --filter=blob:none --depth=1 + checkout - GITLAB_APM_PAT fallback: if git transport raises, DownloadDelegate.download_gitlab_file() falls back to the existing GitLab REST v4 API (mirrors ADO_APM_PAT pattern) - Path containment: ensure_path_within() is applied to the materialized file path after checkout, rejecting symlink/traversal escapes from cloned repos - validate_path_segments() rejects .. traversal sequences before any git work - Reuses the existing clone auth environment (git_env from the downloader); no second origin or cached clone TDD coverage (tests/test_gitlab_git_transport.py): - Acceptance: git-sourced dep with path: fetches file via git (no REST API call) - Self-hosted GitLab API-410 no longer fails - ensure_path_within() rejects symlink-escaping path: after checkout - validate_path_segments() rejects .. traversal before git work - GITLAB_PAT fallback path exercised when git transport raises - Root-level files skip cone setup (no sparse-checkout init) - git failure raises RuntimeError with descriptive message - File missing after checkout raises RuntimeError Docs: updated consumer/authentication.md, consumer/manage-dependencies.md, apm-usage/authentication.md, apm-usage/dependencies.md, and CHANGELOG.md. Stacked on #1732 (#1571). Closes #1014. Credit: @cossons for root-cause analysis of the self-hosted GitLab 410 repro and the ratified design (git-transport-first + thin PAT fallback). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t API (#1740) * feat(deps): fetch path-scoped files over git transport instead of host API For GitLab-sourced deps (SaaS or self-hosted), path: file fetches now go through git sparse/partial checkout (--filter=blob:none + cone sparse-checkout) rather than the host REST API. This is the primary path and fixes self-hosted GitLab instances that return HTTP 410 (REST API disabled), because git transport reuses the same SSH keys and git credential helpers already in use for clones. Design (ratified by board decision): - git-transport-first: fetch_file_via_git_sparse() in the new deps/git_file_transport.py module performs git init + remote add + sparse-checkout init --cone + fetch --filter=blob:none --depth=1 + checkout - GITLAB_APM_PAT fallback: if git transport raises, DownloadDelegate.download_gitlab_file() falls back to the existing GitLab REST v4 API (mirrors ADO_APM_PAT pattern) - Path containment: ensure_path_within() is applied to the materialized file path after checkout, rejecting symlink/traversal escapes from cloned repos - validate_path_segments() rejects .. traversal sequences before any git work - Reuses the existing clone auth environment (git_env from the downloader); no second origin or cached clone TDD coverage (tests/test_gitlab_git_transport.py): - Acceptance: git-sourced dep with path: fetches file via git (no REST API call) - Self-hosted GitLab API-410 no longer fails - ensure_path_within() rejects symlink-escaping path: after checkout - validate_path_segments() rejects .. traversal before git work - GITLAB_PAT fallback path exercised when git transport raises - Root-level files skip cone setup (no sparse-checkout init) - git failure raises RuntimeError with descriptive message - File missing after checkout raises RuntimeError Docs: updated consumer/authentication.md, consumer/manage-dependencies.md, apm-usage/authentication.md, apm-usage/dependencies.md, and CHANGELOG.md. Stacked on #1732 (#1571). Closes #1014. Credit: @cossons for root-cause analysis of the self-hosted GitLab 410 repro and the ratified design (git-transport-first + thin PAT fallback). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(deps): friendly error for subpath embedded in git URL (#872) Adds a parse-time guard that detects when a user embeds an APM primitive subpath inside an explicit git URL form (SCP git@host:path, ssh://, or https://), e.g.: git: git@github.com:org/repo/skills/hello-world.git Such URLs cause git to fail later with a cryptic 'not a valid repository name' error. The guard detects any known APM primitive directory name (skills, agents, prompts, instructions, chatmodes, collections, contexts, memory) appearing as a non-last interior path segment of an explicit git URL, and raises a friendly actionable error pointing at the supported `path:` key instead: [x] A subpath cannot be embedded in a git URL. Use the `path:` key instead: `git: <repo-url>` + `path: <primitive>/<name>` (or the shorthand `org/repo/<primitive>/<name>`). The monorepo-skill-install capability already ships via the `path:` key and the bare shorthand (e.g. `org/repo/skills/hello-world`). No new install feature is added. False-positive check: - GitLab subgroups (git@gitlab.com:group/subgroup/repo.git): safe, no primitive segment in interior - Azure DevOps org/project/repo forms: safe, ADO paths never use primitive names - Plain valid git URLs (2-segment path): safe, length guard returns early - Bare shorthand with subpath (org/repo/skills/foo): safe, not an explicit git URL form The guard is implemented as DependencyReference._check_no_embedded_subpath and called from DependencyReference.parse() after the shorthand-alias and fqdn-conflict guards, before virtual-package detection. Refs #872 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(deps): hard-fail path traversal and attribute GitLab transport Two panel follow-ups on the #1014 GitLab git-transport surface. Supply-chain: download_gitlab_file caught a bare `except Exception` around the git-transport attempt, so a PathTraversalError raised by the path-validation / symlink-containment guards in fetch_file_via_git_sparse was silently swallowed and the request fell through to the REST transport. A rejected traversal must not gain a second transport to probe. PathTraversalError now propagates unchanged; only genuine transport failures fall back to REST. CLI logging: the git->REST fallback transition was `_debug`-only, so triagers had no signal which transport answered a 410. The fallback now emits a verbose `[i]` note carrying the git failure reason, and the REST success notes attribute the GitLab REST API explicitly. Adds a regression test asserting a PathTraversalError in the git path is not swallowed into the REST fallback (mutation-break verified). Updates existing verbose-callback tests that exercised the unmocked-git fallback path to the new transport-attributed messages. addresses CEO follow-up (Supply Chain) and (CLI Logging) on PR #1740 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(deps): scope embedded-subpath guard against false positives The #872 guard `_check_no_embedded_subpath` flagged any non-last path segment matching an APM primitive directory name, which produced two false positives on legitimate URLs: - A GitLab subgroup literally named after a primitive, e.g. `git@gitlab.com:group/skills/repo.git`, where `skills` is a subgroup at index 1 and `repo` is the real repository. - Azure DevOps virtual-path URLs, e.g. `https://dev.azure.com/org/proj/_git/repo/instructions/x`, where the primitive segment is the supported ADO virtual path after `_git/repo`. The embedded-subpath shape is `org/repo` + `<primitive>/<name>`, so a primitive segment now only fires when preceded by a complete org/repo prefix (index >= 2), and ADO URLs (identified by the `_git` marker) are skipped entirely. The canonical malformed form `org/repo/skills/<name>` that #872 targets still hard-fails; no GitHub or GitLab repo path uses `_git`, so real detection is unaffected. A residual deep-subgroup ambiguity (`group/sub/skills/repo`) is documented in code. Adds a regression test for the primitive-named subgroup (mutation-break verified); the ADO false positive is covered by the existing TestParseAdoUrls suite. addresses CEO follow-up (DevX) on PR #1740; Refs #872 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(deps): harden gitlab fallback diagnostics Avoid echoing raw git transport exceptions during the GitLab REST fallback and keep fallback tests hermetic by mocking the git transport failure path. Skip the symlink containment test when the platform cannot create symlinks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold GitLab git transport panel follow-ups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold second GitLab transport panel pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold final GitLab transport panel nits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold residual GitLab transport polish Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(deps): extract identity/semver helpers to identity.py main tightened the file-length guardrail from 2450 to 2100 lines (Stage 1); after merging main, reference.py was 2191 lines. Move the pure, stateless identity helpers (build_dependency_unique_key, build_canonical_dependency_string, _path_segment_pattern, the semver-range guards + segment-pattern constants) into a sibling models/dependency/identity.py and re-export them from reference.py so deps/lockfile.py and install/phases/resolve.py import sites stay unchanged. Behavior-preserving; reference.py now 2099 lines (under guardrail). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: danielmeppiel <danielmeppiel@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sync the Stage 2 complexity/file-length refactor branch with main's 22 feature commits (Hermes #1726, Kiro IDE #1741, multi-host dep identity #1735, same-repo remote path deps #1732, git_file_transport #1740, revision pins #1738, marketplace sourceBase/source parity/inherit description #1736/#1739/#1755, pack --archive .zip #1720, mcp optional registry inputs #1734, and the v0.19.0/v0.20.0 releases). Conflict resolution preserved both sides: main's new features ported through the branch's extracted sibling modules, branch's tightened ruff thresholds (max-statements=120, max-branches=40, max-complexity=35, max-returns=8, max-args=12) and 800-line file limit retained. All 7 CI-mirror lint gates pass; full unit suite green (17099 passed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix(deps): allow same-repo remote path deps
TL;DR
Remote packages can now declare a relative
path:dependency on a sibling package in the same remote repository. The resolver expands the sibling to the parent's remote host/repo/ref and keeps the existing fail-closed guard for absolute paths, repo-root escapes, and cross-repo local paths. Closes #1571.Important
This is intentionally scoped to same-repo sibling
path:resolution only. It is not general workspace semantics, namespaces, or cross-repo path dependency support.Problem (WHY)
Refusing to install local_path dependency './_shared' declared by remote package 'development-backend-dotnet'.I would expect the _shared/ apm package to be installed as well from that same remote repository.Add what the agent lacks, omit what it knowsand because validation should be deterministic:Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action.Approach (WHAT)
path:may resolve to a sibling only if the final path stays inside the same cloned repo root.Implementation (HOW)
src/apm_cli/deps/apm_resolver.pypath:deps, computes the parent repo root fromsource_path+virtual_path, validates containment throughpath_security, and preserves the fallback rejection set.tests/test_apm_resolver.pysrc/apm_cli/deps/path_anchoring.py,src/apm_cli/install/phases/local_content.pypath:deps are same-repo only and still reject unsafe paths.CHANGELOG.md[Unreleased].Diagram
The diagram shows the new resolver fork: eligible sibling paths become same-origin remote virtual deps; unsafe paths stay rejected.
sequenceDiagram participant P as Remote parent apm.yml participant R as Resolver participant C as Parent clone participant D as Downloader P->>R: path: ../shared R->>C: Resolve against parent source path alt Inside same repo root R->>D: Expand to virtual path packages/shared D-->>R: Fetch sibling from same host, repo, and ref else Outside repo root R-->>P: Reject and mark failed endTrade-offs
path:object form as the user-facing syntax rather than adding a new manifest construct.apm_modules, avoiding consumer-filesystem confusion and preserving existing downloader auth/cache behavior.Benefits
Validation
Scenario Evidence
packages/frontenddeclarespath: ../sharedin the same repotests/test_apm_resolver.py::TestRemoteParentLocalPathFailClosed::test_remote_parent_same_repo_sibling_path_expands_to_remote_virtual_deptests/test_apm_resolver.py::TestRemoteParentLocalPathFailClosed::test_remote_parent_path_escape_outside_repo_root_is_rejectedtests/test_apm_resolver.py::TestRemoteParentLocalPathFailClosed::test_remote_parent_path_to_different_repo_clone_is_rejectedCommands run
How to test
packages/frontend/apm.ymldeclares- path: ../shared.apm install <same-repo>/packages/frontend#<branch>.frontendandsharedfrom the same branch/ref.../../../outsideand confirm install rejects it.../other-repo/sharedand confirm install rejects it.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com