Skip to content

Add GitHub Enterprise (GHE) hostname support to dependency parsing - #8

Merged
Daniel Meppiel (danielmeppiel) merged 4 commits into
microsoft:mainfrom
richgo:main
Oct 28, 2025
Merged

Add GitHub Enterprise (GHE) hostname support to dependency parsing#8
Daniel Meppiel (danielmeppiel) merged 4 commits into
microsoft:mainfrom
richgo:main

Conversation

@richgo

@richgoRich James (richgo) commented Oct 17, 2025

Copy link
Copy Markdown
Contributor

Add support for GitHub Enterprise (GHE) hostnames in dependency parsing so apm can correctly detect and normalize repository references hosted on self-managed GitHub instances.
What changed

Update the dependency parsing logic to accept and validate custom GHE hostnames in addition to github.com.
Preserve backwards compatibility for existing github.com parsing behavior.
Add unit tests covering:
standard github.com repo strings
GHE hostname variants (with and without ports)
edge cases and invalid hostnames
Why

Users running packages on self-hosted GitHub instances faced incorrect parsing of dependency references. This change allows apm to work in GHE environments.
How to test

Run the included unit tests: uv run pytest (test docs updated)
Manually verify parsing by providing dependency strings that reference a GHE hostname (e.g., acompany.ghe.com/owner/repo) and confirming correct repo extraction.
Notes

No behavior change for public GitHub users.
Backwards-compatible and covered by tests.

@richgo
Rich James (richgo) marked this pull request as draft October 17, 2025 16:50
remove all hard coded github.com and replace with parameterised
GPT5 mocking the docs... :)
@richgo
Rich James (richgo) marked this pull request as ready for review October 17, 2025 18:59

CopilotAI 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 adds GitHub Enterprise (GHE) hostname support to dependency parsing so apm can correctly detect and normalize repository references hosted on self-managed GitHub instances.

Key Changes:

  • Introduces a new github_host utility module that centralizes GitHub hostname validation and URL construction
  • Updates dependency parsing to accept custom GHE hostnames (e.g., orgname.ghe.com) in addition to github.com
  • Adds environment variable support (GITHUB_HOST, APM_GITHUB_HOSTS) for configuring custom GitHub instances
  • Updates all tests to use the new hostname utilities for consistency

Reviewed Changes

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

Show a summary per file
FileDescription
src/apm_cli/utils/github_host.pyNew utility module providing hostname validation, URL construction, and token sanitization for GitHub and GHE instances
src/apm_cli/models/apm_package.pyUpdates DependencyReference to support GHE hostnames, storing host information and using new utilities for validation
src/apm_cli/deps/github_downloader.pyRefactors to use github_host utilities for URL construction and token sanitization, adds host tracking for enterprise repos
src/apm_cli/adapters/client/copilot.pyReplaces hardcoded hostname allowlist with is_github_hostname() utility
tests/unit/test_github_host.pyNew test file covering default_host(), is_github_hostname(), and sanitize_token_url_in_message()
tests/test_apm_package_models.pyUpdates tests to use github_host utilities and adds GHE URL parsing test cases
tests/test_github_downloader_token_precedence.pyUpdates URL assertions to use github_host utilities for consistency
tests/unit/test_registry_integration.pyUpdates mock URLs to use default_host() for consistency
tests/unit/test_registry_client.pyUpdates mock URLs to use default_host() for consistency
CONTRIBUTING.mdUpdates testing instructions to recommend uv for running tests, adds fallback instructions for standard venv

class DependencyReference:
"""Represents a reference to an APM dependency."""
repo_url: str # e.g., "user/repo" or "github.com/user/repo"
host: Optional[str] = None # Optional host (github.com or enterprise host)

CopilotAIOct 27, 2025

Copy link

Choose a reason for hiding this comment

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

The comment describes the host field but doesn't explain when it's None vs when it's populated. Consider clarifying that host is extracted from the parsed dependency string and defaults to default_host() if not explicitly specified.

Suggested change
host: Optional[str] =None# Optional host (github.com or enterprise host)
host: Optional[str] =None# Host (e.g., github.com or enterprise host); extracted from the dependency string if specified, otherwise None. If None, defaults to default_host() when needed.

Copilot uses AI. Check for mistakes.
Comment threadsrc/apm_cli/models/apm_package.py Outdated
Comment threadsrc/apm_cli/utils/github_host.py
Comment on lines +198 to +200
# Ensure host is set for enterprise repos
if getattr(dep_ref, 'host', None):
self.github_host = dep_ref.host

CopilotAIOct 27, 2025

Copy link

Choose a reason for hiding this comment

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

This pattern of checking and setting self.github_host is duplicated three times (lines 199-200, 213-214, 228-229). Consider extracting this into a helper method or setting it once before the conditional blocks to reduce duplication.

Copilot uses AI. Check for mistakes.
Comment threadsrc/apm_cli/deps/github_downloader.py
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request Oct 28, 2025
The runtime manager was calling setup_runtime_environment() without
passing the env dict, which caused it to create a new os.environ.copy()
and lose any tokens set in the CI environment.
This fixes integration test failures where GITHUB_TOKEN and other
secrets configured in GitHub Actions weren't reaching the runtime
setup scripts, causing unauthenticated API requests and rate limiting.
Fixes integration test failures in PR #8 and similar CI environments.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator

We will need to document GITHUB_HOST and APM_GITHUB_HOSTS based on src/apm_cli/utils/github_host.py‎ for different use cases and scenarios (github.com, GHE Server, GHE DR Cloud)

@danielmeppiel
Daniel Meppiel (danielmeppiel) merged commit 245a4b3 into microsoft:mainOct 28, 2025
Sergio Sisternes (sergio-sisternes-epam) added a commit to sergio-sisternes-epam/apm that referenced this pull request Mar 2, 2026
…osoft#8)
- Drop unittest.TestCase from all 6 test classes
- Replace self.assert* with bare assert statements
- Replace tempfile.TemporaryDirectory with tmp_path fixture
- Remove unused imports (tempfile, unittest)
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request Mar 31, 2026
- Narrow except Exception to except ImportError for lazy marketplace import (comment #1)
- Fix provenance key mismatch: use dep identity instead of canonical for lockfile lookup (comment #2)
- Include subdir in git-subdir source resolution with path traversal validation (comment #3)
- Include relative path in relative source resolution with traversal validation (comment #4)
- Sanitize marketplace name in cache file paths to prevent path traversal (comment #5)
- Fix docs: stale-if-error, not stale-while-revalidate (comment #6)
- Consolidate CHANGELOG entries into single line with (#503) (comment #7)
- Remove unused _SUPPORTED_SOURCE_TYPES set (comment #8)
- Let auth errors propagate in _auto_detect_path instead of swallowing (comment #9)
- Validate marketplace --name against [a-zA-Z0-9._-]+ charset (comment #10)
- Fix doc examples to use identifier-compatible names (comments #11, #12)
- Update tests to match corrected resolver behavior, add traversal tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request Mar 31, 2026
…covery + governance (#503)
* Initial plan
* Initial plan for marketplace integration
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* feat: marketplace integration core implementation
- Add marketplace/ package: models, errors, registry, client, resolver
- Add marketplace CLI commands: add, list, browse, update, remove, search
- Add lockfile provenance fields: discovered_via, marketplace_plugin_name
- Add install hook for NAME@MARKETPLACE syntax pre-parse intercept
- Wire marketplace commands in cli.py
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* docs: add marketplace integration guide and CLI reference
- Create guides/marketplaces.md covering marketplace concepts,
registration, browsing, search, install syntax, provenance tracking,
and cache behavior
- Add apm marketplace and apm search command sections to cli-commands.md
- Update apm install arguments to include NAME@MARKETPLACE syntax
- Update plugins.md Finding Plugins section with marketplace cross-refs
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* docs: fix marketplace.json format and lockfile field names to match implementation
- Use array-based plugins format matching models.py parser expectations
- Use discovered_via and marketplace_plugin_name matching lockfile.py fields
- Document both Copilot CLI (repository/ref) and Claude Code (source) formats
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* docs: fix git-subdir and relative source descriptions to match resolver
- git-subdir uses separate repo and subdir fields
- Relative string sources resolve to marketplace repo subdirectory
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* feat: add marketplace unit tests and docs
- 114 unit tests across 8 test files covering all marketplace modules
- New marketplace guide at docs/src/content/docs/guides/marketplaces.md
- Updated CLI reference with marketplace and search commands
- Updated plugins guide with marketplace integration section
- CHANGELOG entry for marketplace feature
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* refactor: address code review feedback
- Use List[MarketplacePlugin] from typing instead of lowercase generic
- Eliminate duplicated condition in install.py marketplace intercept
- Restructure control flow for clarity
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* fix: address all 12 PR review comments on marketplace integration
- Narrow except Exception to except ImportError for lazy marketplace import (comment #1)
- Fix provenance key mismatch: use dep identity instead of canonical for lockfile lookup (comment #2)
- Include subdir in git-subdir source resolution with path traversal validation (comment #3)
- Include relative path in relative source resolution with traversal validation (comment #4)
- Sanitize marketplace name in cache file paths to prevent path traversal (comment #5)
- Fix docs: stale-if-error, not stale-while-revalidate (comment #6)
- Consolidate CHANGELOG entries into single line with (#503) (comment #7)
- Remove unused _SUPPORTED_SOURCE_TYPES set (comment #8)
- Let auth errors propagate in _auto_detect_path instead of swallowing (comment #9)
- Validate marketplace --name against [a-zA-Z0-9._-]+ charset (comment #10)
- Fix doc examples to use identifier-compatible names (comments #11, #12)
- Update tests to match corrected resolver behavior, add traversal tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Copilot CLI format compatibility and marketplace provenance bugs
Bug #1 - Format incompatibility with awesome-copilot marketplace:
- Parser now accepts 'source' key (Copilot CLI) as type discriminator
fallback when 'type' key is absent, normalizing to 'type' for resolvers
- GitHub source resolver now accepts 'path' field (Copilot CLI) as
virtual subdirectory, same as 'subdir' in git-subdir sources
- Path traversal validation applied to 'path' field
- Fixes: 8 of 62 plugins in awesome-copilot that use github source
objects with 'source'+'path' keys instead of 'type'+'subdir'
Bug #2 - Lockfile provenance never written:
- Root cause: install passed raw marketplace refs (NAME@MARKETPLACE)
as only_packages, but DependencyReference.parse() can't parse those,
so identity filtering removed all deps -> 'already installed'
- Fix: use validated_packages (canonical owner/repo strings) instead
of raw click argument for only_pkgs
Both bugs verified fixed via E2E tests against real marketplaces:
- github/awesome-copilot (62 plugins)
- anthropics/skills (3 plugins)
- microsoft/azure-skills (1 plugin)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: scope marketplace search to QUERY@MARKETPLACE format
Search now requires QUERY@MARKETPLACE (e.g. apm search security@skills)
to eliminate name collisions across marketplaces. Added search_marketplace()
client function for single-marketplace search.
- Rejects bare queries without @ — clear error with usage example
- Validates marketplace exists before searching
- Updated docs/guides/marketplaces.md with new syntax
- 7 test cases: format validation, unknown marketplace, results, no results
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: update CLI reference and plugins guide for scoped search syntax
Align all documentation with QUERY@MARKETPLACE search format.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: use centralized path_security for marketplace traversal checks
Replace 3 ad-hoc '..' in x.split('/') checks in marketplace/resolver.py
with validate_path_segments() from utils/path_security.py. Add
defense-in-depth validate_path_segments() call to _sanitize_cache_name()
in client.py.
This ensures marketplace code uses the same cross-platform path safety
utilities (backslash normalization, single-dot rejection) as the rest
of APM.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: add path safety rule to copilot-instructions.md
Directs contributors to use validate_path_segments() and
ensure_path_within() from utils/path_security.py instead of
ad-hoc traversal checks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
Co-authored-by: danielmeppiel <dmeppiel@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) pushed a commit that referenced this pull request Apr 29, 2026
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.
Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.
Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`
PRs shipping in 0.11.0 (22 commits since v0.10.0):
User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
(BREAKING: `apm marketplace build` removed)
- #1038 `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984 shared/apm.md `apps:` array for cross-org private packages
- #820 `target:` in apm.yml validates at parse time
- #1032 `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms
User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996 drop stray 'specify' from generated file footer
Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918 complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)
Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock: regenerated (version field only)
- CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29
NOTICE drift check passes against the bumped lockfile.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) pushed a commit that referenced this pull request Apr 29, 2026
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.
Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.
Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`
PRs shipping in 0.11.0 (22 commits since v0.10.0):
User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
(BREAKING: `apm marketplace build` removed)
- #1038 `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984 shared/apm.md `apps:` array for cross-org private packages
- #820 `target:` in apm.yml validates at parse time
- #1032 `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms
User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996 drop stray 'specify' from generated file footer
Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918 complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)
Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock: regenerated (version field only)
- CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29
NOTICE drift check passes against the bumped lockfile.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request Apr 29, 2026
* chore(release): cut 0.11.0
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.
Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.
Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`
PRs shipping in 0.11.0 (22 commits since v0.10.0):
User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
(BREAKING: `apm marketplace build` removed)
- #1038 `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984 shared/apm.md `apps:` array for cross-org private packages
- #820 `target:` in apm.yml validates at parse time
- #1032 `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms
User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996 drop stray 'specify' from generated file footer
Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918 complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)
Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock: regenerated (version field only)
- CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29
NOTICE drift check passes against the bumped lockfile.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(changelog): tighten 0.11.0 entries to lead with user impact
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request Apr 30, 2026
…#1073)
* docs(notice): rename NOTICE.md -> NOTICE; add CLA third-party section
Two changes, one file rename:
1. Rename NOTICE.md -> NOTICE, matching the Apache / CNCF convention used
by upstream third-party-attribution files (kubernetes-sigs/kro,
kubernetes-sigs/headlamp, etc.). The .md extension was non-idiomatic
for a generated legal artifact -- NOTICE files are read by tooling
(license scanners, SBOM generators) that match on the bare filename.
Generator (scripts/generate-notice.py), Makefile target, and the
NOTICE Drift Check workflow are all updated to operate on the
extension-less path.
2. Add a 'Submitted on behalf of a third-party' section to NOTICE,
crediting five contributors whose pull requests landed before the
microsoft-github-policy-service CLA bot recorded a signature on
file. The repo transferred from danielmeppiel/awd-cli to the
microsoft org; some early PRs predate CLA enforcement, and we
could not retroactively reach all contributors. Mirrors section 7
of common CLA texts (the wording adopted by CNCF NOTICE files).
Driven by a new _third_party_submissions block in
scripts/notice-metadata.yaml -- legally-significant wording stays
alongside the per-component data, not buried in code.
Contributors named (verified via Check Runs API against the
microsoft-github-policy-service app, license/cla check on every
merged PR by each suspected author):
- @pofallon (PR #4)
- @richgo (PRs #8, #25, #26, #33, #34)
- @ryanfk (PR #92 -- bot ran with conclusion=null,
output: 'Contributor License Agreement is not agreed yet.')
- @foutoucour (PR #108)
- @Jah-yee (PR #184)
Listed contributors who later sign the CLA (or who were signed
under a different GitHub account at the time) can request removal
via issue.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(notice): trim third-party section preamble
Strip the historical/CNCF-citation paragraph and the verbatim CLA-section-7
quote. Keep only the active sentence (what the listing means + how to
request removal).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(notice): address PR #1073 review
Three fixes from copilot-pull-request-reviewer:
1. Drop spurious leading '---' separator in the third-party-submissions
renderer. render_component already ends each component with '---\n\n',
so prepending another '---' produced two consecutive separators in
NOTICE. Verified: separator count dropped from 17 to 16.
2. Sweep stale 'NOTICE.md' references in scripts/generate-notice.py
(top-level docstring, Modes section, ComponentMeta and DepSpec field
docstrings). The constant was renamed; the docs lagged.
3. Append (#1073) PR refs to both CHANGELOG entries; ASCII-correct the
arrow ('->' instead of '->').
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot-rework@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) pushed a commit to stbenjam/apm that referenced this pull request Apr 30, 2026
…e-commands
Address the 5 required findings from the APM Review Panel on PR microsoft#1046
(round 2) by replacing the hardcoded cursor branch and the warn()-only
notification with a unified consent gate.
Changes:
- targets.py: add TargetProfile.requires_executable_consent (default
False); set True on the cursor entry only. Documents the asymmetric-
consent risk for claude/opencode/gemini in the docstring (panel
finding microsoft#5: deferred to avoid breaking existing installs).
- command_integrator.py: introduce should_deploy_executable_commands()
helper. integrate_commands_for_target now accepts
allow_executable_commands and skips deployment with a warn-level
diagnostic naming the exact CLI flag when consent is required but
absent. Replaces the cursor-branded warn() block (Threat microsoft#8: warn
is notification, not consent).
- install.py: add --allow-executable-commands click option and field
on the local InstallContext; thread to _install_apm_dependencies
and through InstallRequest -> InstallService -> run_install_pipeline.
- services.py / pipeline.py / context.py / template.py / phases:
plumb allow_executable_commands through the install pipeline. The
dispatch loop only forwards the kwarg to the commands integrator.
Tests:
- New TestExecutableConsentGate class covering: helper short-circuit
for non-consent targets, helper block/allow on cursor, gated-skip
result + warning when flag absent, deploy when flag present, and
parametrized regression that claude/opencode/gemini are unaffected.
- Existing cursor deployment tests updated to pass
allow_executable_commands=True.
- test_install_context.py: include allow_executable_commands in the
expected fields tuple.
Docs:
- cli-commands.md and packages/apm-guide commands.md: document the new
flag and the cursor opt-in.
- CHANGELOG.md: reframe the cursor entry around the user-visible
opt-in flag and Threat microsoft#8 rationale.
Closes panel findings 1, 3, 4 from PR microsoft#1046 round 2. Finding 5
(asymmetric consent across slash-command targets) is documented in
the TargetProfile docstring as deferred -- flipping the flag for
claude/opencode/gemini would silently break existing installs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) pushed a commit to stbenjam/apm that referenced this pull request Apr 30, 2026
…efault, --allow-cursor-commands
Round 4 panel raised 8 required findings against the cursor-commands
gate. Round 5 addresses all of them:
Architecture (python-architect)
- Add frozen IntegrateOptions dataclass with extra_kwargs(prim) helper
in install/services.py. Dispatch loop is now branchless — no more
inline 'if _prim_name == "commands"' special case.
- Hoist pkg_name once at top of integrate_commands_for_target.
- Move executable-commands gate before find_prompt_files in the happy
path; keep one find call inside the gate-skipped branch so the
user-facing skip warning includes an exact count.
CLI logging (cli-logging-expert)
- _post_install_summary surfaces the consent flag in --verbose output:
' allow-cursor-commands: true|false' (gated on logger.verbose).
- Add _should_emit_passthrough_notice helper + _passthrough_notified
instance set on CommandIntegrator. One-shot info diagnostic fires
once per (install, target) on Cursor installs explaining that
Cursor command files keep the Claude-compatible frontmatter subset
intentionally for cross-tool compatibility.
- Combine missing-.cursor/-dir hint with consent-flag mention so users
see both signals in one diagnostic.
DevX UX (devx-ux-expert)
- Rename CLI flag --allow-executable-commands -> --allow-cursor-commands
(target-scoped, user-friendly). Internal pipeline parameter remains
allow_executable_commands so future targets can share the gate
plumbing via their own --allow-<target>-commands flags. Mapping
happens at the CLI boundary in install().
- Update skip warning to name the new flag.
Supply-chain security (supply-chain-security-expert)
- Flip TargetProfile.requires_executable_consent default to True at
the dataclass level. New targets now refuse command deployment unless
an opt-out is explicit. Add explicit =False with rationale comment
on each existing target (claude, opencode, gemini), each referencing
enterprise/security.md.
- Add new '## Post-install code execution (Threat microsoft#8)' section to
docs/src/content/docs/enterprise/security.md with the per-target
posture matrix (cursor=gated, claude/opencode/gemini=opt-out),
rationale for the asymmetry, npm --ignore-scripts mental model,
and future-targets guidance.
- Remove dead bool() cast in should_deploy_executable_commands.
- Remove inline TODO comments referencing PR URL.
OSS growth (oss-growth-hacker)
- Rewrite CHANGELOG entry to lead with user outcome ('Cursor users
can install APM package slash commands with one flag…'). Security
rationale moved to end. Cursor 1.6+ lifecycle note included inline.
- Add Cursor 1.6+ lifecycle caveat to:
- docs/.../integrations/ide-tool-integration.md
- docs/.../reference/cli-commands.md
- packages/apm-guide/.apm/skills/apm-usage/commands.md
- packages/apm-guide/.apm/skills/apm-usage/package-authoring.md
Tests
- Update test assertions to use new flag name.
- Add requires_executable_consent=False to the synthetic test profile
in tests/unit/integration/test_data_driven_dispatch.py (consequence
of fail-closed default).
- Full unit suite: 6924 passed.
Round 5 panel: 6/6 specialists APPROVE; CEO synthesizer APPROVE.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) pushed a commit to stbenjam/apm that referenced this pull request Apr 30, 2026
…fault
Cursor slash commands have identical invocation semantics to
Claude/OpenCode/Gemini -- the user must type /cmdname to invoke. They
do not auto-execute on disk-write or IDE startup, so the npm
--ignore-scripts / Threat microsoft#8 framing was incorrect. The gate created
artificial asymmetry between targets with identical UX and violated
portable-by-manifest.
Removed:
- --allow-cursor-commands CLI flag
- TargetProfile.requires_executable_consent field
- should_deploy_executable_commands() helper + gate block
- IntegrateOptions per-primitive routing (only consumer was the gate)
- allow_executable_commands plumbing through 7 layers
- Threat microsoft#8 framing from docs + CHANGELOG
Kept:
- Cursor commands PrimitiveMapping (the actual feature)
- Lossy-frontmatter diagnostics.warn() per file (real value: surfaces
dropped Cursor-specific keys to package authors)
- Cursor 1.6+ lifecycle note (Cursor de-emphasizing commands)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request May 3, 2026
* feat(cursor): add slash command support for Cursor 1.6+
Register the "commands" primitive in the Cursor target profile so
apm install deploys .prompt.md files to .cursor/commands/*.md
when a .cursor/ directory is present.
Cursor 1.6 introduced custom slash commands [1] stored as plain
Markdown in .cursor/commands/. Cursor has since de-emphasized
commands in favor of skills/rules [2], but as long as it supports
the commands surface it makes sense to deploy to it.
Cursor itself does not consume YAML frontmatter, but we
deliberately emit Claude-compatible frontmatter (description,
allowed-tools, arguments, argument-hint) rather than stripping it.
The frontmatter is harmless to Cursor -- the file is just a
context-to-LLM routing mechanism -- and in practice, leaving
structured input hints in the file makes the commands noticeably
more successful at inferring required inputs from the user.
This reuses the existing integrate_command() path (the else branch
in CommandIntegrator.integrate_commands_for_target), so the only
production code change is three lines in targets.py.
Testing strategy:
- Unit tests: opt-in guard, single/multi deploy, frontmatter
preservation, sync removal, missing-dir graceful no-op
- Integration tests: target gating dispatch (mocked integrators)
and full end-to-end through integrate_package_primitives with
real integrators verifying file output
[1] https://cursor.com/changelog/1-6
[2] https://cursor.com/docs/plugins
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cursor): address review feedback on command integration
- Docs: change "frontmatter preserved" to "normalized to supported
command frontmatter" -- the shared command transformer keeps only
description, allowed-tools, model, and argument-hint, dropping
unknown keys like author or custom fields
- Tests: rename test_frontmatter_preserved to
test_frontmatter_normalized_to_supported_subset and assert that
non-whitelisted fields (author, custom-field) are dropped
- Tests: fix test_sync_removes_apm_commands to use manifest-based
sync (managed_files set) instead of the legacy *-apm.md glob
fallback, matching how Cursor commands are actually deployed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: ruff import organization in cursor command tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test/integration: address Copilot review on Cursor commands (#1046)
- targets.py: switch Cursor commands format_id from cursor_command to
claude_command and document why. The shared command transformer is
what actually runs today; the previous tag mis-advertised a Cursor-
specific writer that does not exist and would silently lie about
preserving Cursor-only frontmatter (author/input/mcp/parameters).
Switch back to a dedicated cursor_command tag only when an integrator
branch is added that preserves full Cursor metadata verbatim.
- test_command_integrator.py: drop redundant per-test reimports of
shutil/tempfile (PEP 8 nit, both are imported at module top).
Other Copilot comments were already covered:
- docs already say 'normalized to supported command frontmatter'
- test_sync_removes_managed_commands exercises Cursor manifest mode
- test_frontmatter_normalized_to_supported_subset asserts non-whitelisted
fields (author, custom-field) are dropped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style: ruff format test_command_integrator.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(cursor-commands): warn on dropped frontmatter, traversal guards, target-aware diagnostics
Address all 10 required findings from APM Review Panel on PR #1046:
- Extract _make_package() to module-level test helper (DRY across 6 classes)
- Make integrate_command() target-aware via target_name kwarg; emit
'command arguments' instead of Claude-branded message for Cursor installs
- Detect frontmatter keys not preserved by the shared claude_command
transformer (author, mcp, parameters, ...) and surface diagnostics.warn()
per file so users see the lossy transform at install time
- Add install-time info note when .cursor/ is missing instead of silently
skipping deployment (improves CI-vs-dev discoverability)
- Validate base_name with validate_path_segments() and ensure_path_within()
before joining into commands_dir to close path-traversal vector via
malicious package filenames
- Harden find_files_by_glob() to reject hardlinks/files whose resolved path
escapes package_path (symlink check did not catch hardlinks)
- Surface a one-time IDE consent warning per package when Cursor commands
are deployed since they become invokable as IDE slash commands
- Add TODO(cursor-command-format) with tracking issue URL on both targets.py
and integrate_command() to make the shared-transformer debt trackable
- Add commands_cursor identity entry to _BUCKET_ALIASES for explicit
documentation parity with commands_opencode
- CHANGELOG entry under [Unreleased] and ide-tool-integration.md note
about the frontmatter subset limitation
- New TestCursorCommandPanelFindings regression class covers all five
panel-driven behaviours (dropped-key warn, traversal rejection,
target-aware diagnostics, IDE consent, skip note)
Verified: 6939 unit tests pass; ruff format + ruff check clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(install): gate cursor command deployment behind --allow-executable-commands
Address the 5 required findings from the APM Review Panel on PR #1046
(round 2) by replacing the hardcoded cursor branch and the warn()-only
notification with a unified consent gate.
Changes:
- targets.py: add TargetProfile.requires_executable_consent (default
False); set True on the cursor entry only. Documents the asymmetric-
consent risk for claude/opencode/gemini in the docstring (panel
finding #5: deferred to avoid breaking existing installs).
- command_integrator.py: introduce should_deploy_executable_commands()
helper. integrate_commands_for_target now accepts
allow_executable_commands and skips deployment with a warn-level
diagnostic naming the exact CLI flag when consent is required but
absent. Replaces the cursor-branded warn() block (Threat #8: warn
is notification, not consent).
- install.py: add --allow-executable-commands click option and field
on the local InstallContext; thread to _install_apm_dependencies
and through InstallRequest -> InstallService -> run_install_pipeline.
- services.py / pipeline.py / context.py / template.py / phases:
plumb allow_executable_commands through the install pipeline. The
dispatch loop only forwards the kwarg to the commands integrator.
Tests:
- New TestExecutableConsentGate class covering: helper short-circuit
for non-consent targets, helper block/allow on cursor, gated-skip
result + warning when flag absent, deploy when flag present, and
parametrized regression that claude/opencode/gemini are unaffected.
- Existing cursor deployment tests updated to pass
allow_executable_commands=True.
- test_install_context.py: include allow_executable_commands in the
expected fields tuple.
Docs:
- cli-commands.md and packages/apm-guide commands.md: document the new
flag and the cursor opt-in.
- CHANGELOG.md: reframe the cursor entry around the user-visible
opt-in flag and Threat #8 rationale.
Closes panel findings 1, 3, 4 from PR #1046 round 2. Finding 5
(asymmetric consent across slash-command targets) is documented in
the TargetProfile docstring as deferred -- flipping the flag for
claude/opencode/gemini would silently break existing installs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(install): address round 4 panel — IntegrateOptions, fail-closed default, --allow-cursor-commands
Round 4 panel raised 8 required findings against the cursor-commands
gate. Round 5 addresses all of them:
Architecture (python-architect)
- Add frozen IntegrateOptions dataclass with extra_kwargs(prim) helper
in install/services.py. Dispatch loop is now branchless — no more
inline 'if _prim_name == "commands"' special case.
- Hoist pkg_name once at top of integrate_commands_for_target.
- Move executable-commands gate before find_prompt_files in the happy
path; keep one find call inside the gate-skipped branch so the
user-facing skip warning includes an exact count.
CLI logging (cli-logging-expert)
- _post_install_summary surfaces the consent flag in --verbose output:
' allow-cursor-commands: true|false' (gated on logger.verbose).
- Add _should_emit_passthrough_notice helper + _passthrough_notified
instance set on CommandIntegrator. One-shot info diagnostic fires
once per (install, target) on Cursor installs explaining that
Cursor command files keep the Claude-compatible frontmatter subset
intentionally for cross-tool compatibility.
- Combine missing-.cursor/-dir hint with consent-flag mention so users
see both signals in one diagnostic.
DevX UX (devx-ux-expert)
- Rename CLI flag --allow-executable-commands -> --allow-cursor-commands
(target-scoped, user-friendly). Internal pipeline parameter remains
allow_executable_commands so future targets can share the gate
plumbing via their own --allow-<target>-commands flags. Mapping
happens at the CLI boundary in install().
- Update skip warning to name the new flag.
Supply-chain security (supply-chain-security-expert)
- Flip TargetProfile.requires_executable_consent default to True at
the dataclass level. New targets now refuse command deployment unless
an opt-out is explicit. Add explicit =False with rationale comment
on each existing target (claude, opencode, gemini), each referencing
enterprise/security.md.
- Add new '## Post-install code execution (Threat #8)' section to
docs/src/content/docs/enterprise/security.md with the per-target
posture matrix (cursor=gated, claude/opencode/gemini=opt-out),
rationale for the asymmetry, npm --ignore-scripts mental model,
and future-targets guidance.
- Remove dead bool() cast in should_deploy_executable_commands.
- Remove inline TODO comments referencing PR URL.
OSS growth (oss-growth-hacker)
- Rewrite CHANGELOG entry to lead with user outcome ('Cursor users
can install APM package slash commands with one flag…'). Security
rationale moved to end. Cursor 1.6+ lifecycle note included inline.
- Add Cursor 1.6+ lifecycle caveat to:
- docs/.../integrations/ide-tool-integration.md
- docs/.../reference/cli-commands.md
- packages/apm-guide/.apm/skills/apm-usage/commands.md
- packages/apm-guide/.apm/skills/apm-usage/package-authoring.md
Tests
- Update test assertions to use new flag name.
- Add requires_executable_consent=False to the synthetic test profile
in tests/unit/integration/test_data_driven_dispatch.py (consequence
of fail-closed default).
- Full unit suite: 6924 passed.
Round 5 panel: 6/6 specialists APPROVE; CEO synthesizer APPROVE.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(cursor): drop --allow-cursor-commands gate, deploy commands by default
Cursor slash commands have identical invocation semantics to
Claude/OpenCode/Gemini -- the user must type /cmdname to invoke. They
do not auto-execute on disk-write or IDE startup, so the npm
--ignore-scripts / Threat #8 framing was incorrect. The gate created
artificial asymmetry between targets with identical UX and violated
portable-by-manifest.
Removed:
- --allow-cursor-commands CLI flag
- TargetProfile.requires_executable_consent field
- should_deploy_executable_commands() helper + gate block
- IntegrateOptions per-primitive routing (only consumer was the gate)
- allow_executable_commands plumbing through 7 layers
- Threat #8 framing from docs + CHANGELOG
Kept:
- Cursor commands PrimitiveMapping (the actual feature)
- Lossy-frontmatter diagnostics.warn() per file (real value: surfaces
dropped Cursor-specific keys to package authors)
- Cursor 1.6+ lifecycle note (Cursor de-emphasizing commands)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(cursor-commands): act on PR #1046 panel review
Apply the actionable findings from the apm-review-panel critique on
PR #1046:
* docs: fix `.opencode/command/*.md` -> `.opencode/commands/*.md`
typo in the security guide (factual error in IDE integration map).
* CHANGELOG: rewrite Cursor entry user-first; lifecycle caveat moves
to a trailing sentence so the value prop reads first.
* integrator: gate the one-shot 'cross-tool compatibility' notice on
whether at least one file in the batch had dropped frontmatter
keys. Previously fired on every Cursor install -> noise on the
happy path. Cursor installs whose prompts only use the cross-tool
subset now emit zero notice. Other targets (claude, opencode)
unaffected.
* integrator: rewrite the dropped-keys warn message to drop internal
jargon ('shared command transformer'); use target-name framing
('not supported for cursor commands') and surface the canonical
kebab-case key list (no camelCase aliases).
* integrator: upgrade post-transform scan_text from WARN_POLICY to
BLOCK_POLICY. When a critical finding is detected (e.g. a hidden
tag char introduced by link resolution), skip the write and account
for it in result.files_skipped. Pre-install BLOCK gate already
scans source files, so this is defense-in-depth for the
transform-introduced edge case; no regression for legitimate
packages.
* base_integrator: actually close the hardlink containment gap in
find_files_by_glob. The prior is_relative_to() check did NOT
catch hardlinks -- a hardlink resolves to its own path inside the
package root. Add an st_nlink > 1 reject to prevent a malicious
package shipping a hardlink to (e.g.) /etc/passwd from being read
via integration. Update the inline comment to be accurate about
what each guard actually does.
Tests:
* tests/unit/integration/test_base_integrator.py: new
test_hardlink_escaping_package_root_is_excluded covering the
st_nlink reject; cleaned up F821 noqa via real pytest import.
* tests/unit/integration/test_command_integrator.py: 4 new tests in
TestCursorCommandPanelFindings:
- test_passthrough_notice_suppressed_on_clean_install
- test_passthrough_notice_emitted_when_any_file_drops_keys
- test_dropped_keys_warn_uses_user_facing_wording
- test_critical_security_finding_blocks_write
Updated the existing test_dropped_frontmatter_keys_warn assertion
to match the new wording.
* tests/integration/test_marketplace_plugin_integration.py: 2 new
integration tests locking in cursor 1.6+ command deployment via
the install pipeline (deploy when .cursor/ exists; respect opt-in
by NOT creating .cursor/ when missing).
Verification:
uv run --extra dev ruff check src/ tests/ -> All checks passed!
uv run --extra dev ruff format --check src/ tests/ -> 627 files OK
uv run --extra dev pytest tests/unit ... -> 7222 passed
Refs: #1046 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot-rework@github.com>
Co-authored-by: Daniel Meppiel <51440732+danielmeppiel@users.noreply.github.com>
Sergio Sisternes (sergio-sisternes-epam) pushed a commit that referenced this pull request May 19, 2026
The runtime manager was calling setup_runtime_environment() without
passing the env dict, which caused it to create a new os.environ.copy()
and lose any tokens set in the CI environment.
This fixes integration test failures where GITHUB_TOKEN and other
secrets configured in GitHub Actions weren't reaching the runtime
setup scripts, causing unauthenticated API requests and rate limiting.
Fixes integration test failures in PR #8 and similar CI environments.
Sergio Sisternes (sergio-sisternes-epam) added a commit that referenced this pull request May 19, 2026
- Drop unittest.TestCase from all 6 test classes
- Replace self.assert* with bare assert statements
- Replace tempfile.TemporaryDirectory with tmp_path fixture
- Remove unused imports (tempfile, unittest)
Sergio Sisternes (sergio-sisternes-epam) pushed a commit that referenced this pull request May 19, 2026
…covery + governance (#503)
* Initial plan
* Initial plan for marketplace integration
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* feat: marketplace integration core implementation
- Add marketplace/ package: models, errors, registry, client, resolver
- Add marketplace CLI commands: add, list, browse, update, remove, search
- Add lockfile provenance fields: discovered_via, marketplace_plugin_name
- Add install hook for NAME@MARKETPLACE syntax pre-parse intercept
- Wire marketplace commands in cli.py
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* docs: add marketplace integration guide and CLI reference
- Create guides/marketplaces.md covering marketplace concepts,
registration, browsing, search, install syntax, provenance tracking,
and cache behavior
- Add apm marketplace and apm search command sections to cli-commands.md
- Update apm install arguments to include NAME@MARKETPLACE syntax
- Update plugins.md Finding Plugins section with marketplace cross-refs
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* docs: fix marketplace.json format and lockfile field names to match implementation
- Use array-based plugins format matching models.py parser expectations
- Use discovered_via and marketplace_plugin_name matching lockfile.py fields
- Document both Copilot CLI (repository/ref) and Claude Code (source) formats
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* docs: fix git-subdir and relative source descriptions to match resolver
- git-subdir uses separate repo and subdir fields
- Relative string sources resolve to marketplace repo subdirectory
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* feat: add marketplace unit tests and docs
- 114 unit tests across 8 test files covering all marketplace modules
- New marketplace guide at docs/src/content/docs/guides/marketplaces.md
- Updated CLI reference with marketplace and search commands
- Updated plugins guide with marketplace integration section
- CHANGELOG entry for marketplace feature
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* refactor: address code review feedback
- Use List[MarketplacePlugin] from typing instead of lowercase generic
- Eliminate duplicated condition in install.py marketplace intercept
- Restructure control flow for clarity
Agent-Logs-Url: https://github.com/microsoft/apm/sessions/12a9b016-7930-41b8-a340-c64f11486b71
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
* fix: address all 12 PR review comments on marketplace integration
- Narrow except Exception to except ImportError for lazy marketplace import (comment #1)
- Fix provenance key mismatch: use dep identity instead of canonical for lockfile lookup (comment #2)
- Include subdir in git-subdir source resolution with path traversal validation (comment #3)
- Include relative path in relative source resolution with traversal validation (comment #4)
- Sanitize marketplace name in cache file paths to prevent path traversal (comment #5)
- Fix docs: stale-if-error, not stale-while-revalidate (comment #6)
- Consolidate CHANGELOG entries into single line with (#503) (comment #7)
- Remove unused _SUPPORTED_SOURCE_TYPES set (comment #8)
- Let auth errors propagate in _auto_detect_path instead of swallowing (comment #9)
- Validate marketplace --name against [a-zA-Z0-9._-]+ charset (comment #10)
- Fix doc examples to use identifier-compatible names (comments #11, #12)
- Update tests to match corrected resolver behavior, add traversal tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Copilot CLI format compatibility and marketplace provenance bugs
Bug #1 - Format incompatibility with awesome-copilot marketplace:
- Parser now accepts 'source' key (Copilot CLI) as type discriminator
fallback when 'type' key is absent, normalizing to 'type' for resolvers
- GitHub source resolver now accepts 'path' field (Copilot CLI) as
virtual subdirectory, same as 'subdir' in git-subdir sources
- Path traversal validation applied to 'path' field
- Fixes: 8 of 62 plugins in awesome-copilot that use github source
objects with 'source'+'path' keys instead of 'type'+'subdir'
Bug #2 - Lockfile provenance never written:
- Root cause: install passed raw marketplace refs (NAME@MARKETPLACE)
as only_packages, but DependencyReference.parse() can't parse those,
so identity filtering removed all deps -> 'already installed'
- Fix: use validated_packages (canonical owner/repo strings) instead
of raw click argument for only_pkgs
Both bugs verified fixed via E2E tests against real marketplaces:
- github/awesome-copilot (62 plugins)
- anthropics/skills (3 plugins)
- microsoft/azure-skills (1 plugin)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: scope marketplace search to QUERY@MARKETPLACE format
Search now requires QUERY@MARKETPLACE (e.g. apm search security@skills)
to eliminate name collisions across marketplaces. Added search_marketplace()
client function for single-marketplace search.
- Rejects bare queries without @ — clear error with usage example
- Validates marketplace exists before searching
- Updated docs/guides/marketplaces.md with new syntax
- 7 test cases: format validation, unknown marketplace, results, no results
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: update CLI reference and plugins guide for scoped search syntax
Align all documentation with QUERY@MARKETPLACE search format.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: use centralized path_security for marketplace traversal checks
Replace 3 ad-hoc '..' in x.split('/') checks in marketplace/resolver.py
with validate_path_segments() from utils/path_security.py. Add
defense-in-depth validate_path_segments() call to _sanitize_cache_name()
in client.py.
This ensures marketplace code uses the same cross-platform path safety
utilities (backslash normalization, single-dot rejection) as the rest
of APM.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: add path safety rule to copilot-instructions.md
Directs contributors to use validate_path_segments() and
ensure_path_within() from utils/path_security.py instead of
ad-hoc traversal checks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com>
Co-authored-by: danielmeppiel <dmeppiel@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sergio Sisternes (sergio-sisternes-epam) pushed a commit that referenced this pull request May 19, 2026
* chore(release): cut 0.11.0
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps
pyproject.toml + uv.lock to 0.11.0.
Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because
this release ships one BREAKING removal (`apm marketplace build` -> exits 2,
use `apm pack`) plus several net-new features (Dev Container Feature,
Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack`
unification, multi-org `apps[]`). Strict semver in 0.x: minor for
features-with-break, patch only for bugfixes.
Milestone admin (done out-of-band):
- Renamed milestone #8 `0.10.1` -> `0.11.0`
- Created milestone #9 `0.12.0` as next-up bucket
- Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0`
- 6 closed items stay in `0.11.0`
PRs shipping in 0.11.0 (22 commits since v0.10.0):
User-facing features:
- #1042/#722 `apm pack` unifies bundle + marketplace.json
(BREAKING: `apm marketplace build` removed)
- #1038 `marketplace:` block in apm.yml + `apm marketplace migrate`
- #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives
- #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli`
- #982/#984 shared/apm.md `apps:` array for cross-org private packages
- #820 `target:` in apm.yml validates at parse time
- #1032 `apm marketplace add` honors manifest.name (Claude Code parity)
- #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms
User-facing fixes:
- #1015 ADO Entra ID auth + `apm install --update` pre-flight abort
- #1019/#1020 GEMINI.md only created when target requested
- #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms
- #1018 POSIX paths in auto-discovery output (Windows compat)
- #996 drop stray 'specify' from generated file footer
Maintainer tooling:
- #1043 NOTICE.md per CELA template
- #1045/#1044 NOTICE drift gate + license-policy gate in CI
- #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut)
- #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1
- #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file
- #1022 review-panel: true fan-out + binary verdict + label automation
- #918 complexity audit + benchmarks suite
- #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder)
Files changed:
- pyproject.toml: 0.10.0 -> 0.11.0
- uv.lock: regenerated (version field only)
- CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29
NOTICE drift check passes against the bumped lockfile.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(changelog): tighten 0.11.0 entries to lead with user impact
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sergio Sisternes (sergio-sisternes-epam) pushed a commit that referenced this pull request May 19, 2026
…#1073)
* docs(notice): rename NOTICE.md -> NOTICE; add CLA third-party section
Two changes, one file rename:
1. Rename NOTICE.md -> NOTICE, matching the Apache / CNCF convention used
by upstream third-party-attribution files (kubernetes-sigs/kro,
kubernetes-sigs/headlamp, etc.). The .md extension was non-idiomatic
for a generated legal artifact -- NOTICE files are read by tooling
(license scanners, SBOM generators) that match on the bare filename.
Generator (scripts/generate-notice.py), Makefile target, and the
NOTICE Drift Check workflow are all updated to operate on the
extension-less path.
2. Add a 'Submitted on behalf of a third-party' section to NOTICE,
crediting five contributors whose pull requests landed before the
microsoft-github-policy-service CLA bot recorded a signature on
file. The repo transferred from danielmeppiel/awd-cli to the
microsoft org; some early PRs predate CLA enforcement, and we
could not retroactively reach all contributors. Mirrors section 7
of common CLA texts (the wording adopted by CNCF NOTICE files).
Driven by a new _third_party_submissions block in
scripts/notice-metadata.yaml -- legally-significant wording stays
alongside the per-component data, not buried in code.
Contributors named (verified via Check Runs API against the
microsoft-github-policy-service app, license/cla check on every
merged PR by each suspected author):
- @pofallon (PR #4)
- @richgo (PRs #8, #25, #26, #33, #34)
- @ryanfk (PR #92 -- bot ran with conclusion=null,
output: 'Contributor License Agreement is not agreed yet.')
- @foutoucour (PR #108)
- @Jah-yee (PR #184)
Listed contributors who later sign the CLA (or who were signed
under a different GitHub account at the time) can request removal
via issue.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(notice): trim third-party section preamble
Strip the historical/CNCF-citation paragraph and the verbatim CLA-section-7
quote. Keep only the active sentence (what the listing means + how to
request removal).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(notice): address PR #1073 review
Three fixes from copilot-pull-request-reviewer:
1. Drop spurious leading '---' separator in the third-party-submissions
renderer. render_component already ends each component with '---\n\n',
so prepending another '---' produced two consecutive separators in
NOTICE. Verified: separator count dropped from 17 to 16.
2. Sweep stale 'NOTICE.md' references in scripts/generate-notice.py
(top-level docstring, Modes section, ComponentMeta and DepSpec field
docstrings). The constant was renamed; the docs lagged.
3. Append (#1073) PR refs to both CHANGELOG entries; ASCII-correct the
arrow ('->' instead of '->').
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot-rework@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sergio Sisternes (sergio-sisternes-epam) pushed a commit that referenced this pull request May 19, 2026
* feat(cursor): add slash command support for Cursor 1.6+
Register the "commands" primitive in the Cursor target profile so
apm install deploys .prompt.md files to .cursor/commands/*.md
when a .cursor/ directory is present.
Cursor 1.6 introduced custom slash commands [1] stored as plain
Markdown in .cursor/commands/. Cursor has since de-emphasized
commands in favor of skills/rules [2], but as long as it supports
the commands surface it makes sense to deploy to it.
Cursor itself does not consume YAML frontmatter, but we
deliberately emit Claude-compatible frontmatter (description,
allowed-tools, arguments, argument-hint) rather than stripping it.
The frontmatter is harmless to Cursor -- the file is just a
context-to-LLM routing mechanism -- and in practice, leaving
structured input hints in the file makes the commands noticeably
more successful at inferring required inputs from the user.
This reuses the existing integrate_command() path (the else branch
in CommandIntegrator.integrate_commands_for_target), so the only
production code change is three lines in targets.py.
Testing strategy:
- Unit tests: opt-in guard, single/multi deploy, frontmatter
preservation, sync removal, missing-dir graceful no-op
- Integration tests: target gating dispatch (mocked integrators)
and full end-to-end through integrate_package_primitives with
real integrators verifying file output
[1] https://cursor.com/changelog/1-6
[2] https://cursor.com/docs/plugins
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cursor): address review feedback on command integration
- Docs: change "frontmatter preserved" to "normalized to supported
command frontmatter" -- the shared command transformer keeps only
description, allowed-tools, model, and argument-hint, dropping
unknown keys like author or custom fields
- Tests: rename test_frontmatter_preserved to
test_frontmatter_normalized_to_supported_subset and assert that
non-whitelisted fields (author, custom-field) are dropped
- Tests: fix test_sync_removes_apm_commands to use manifest-based
sync (managed_files set) instead of the legacy *-apm.md glob
fallback, matching how Cursor commands are actually deployed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: ruff import organization in cursor command tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test/integration: address Copilot review on Cursor commands (#1046)
- targets.py: switch Cursor commands format_id from cursor_command to
claude_command and document why. The shared command transformer is
what actually runs today; the previous tag mis-advertised a Cursor-
specific writer that does not exist and would silently lie about
preserving Cursor-only frontmatter (author/input/mcp/parameters).
Switch back to a dedicated cursor_command tag only when an integrator
branch is added that preserves full Cursor metadata verbatim.
- test_command_integrator.py: drop redundant per-test reimports of
shutil/tempfile (PEP 8 nit, both are imported at module top).
Other Copilot comments were already covered:
- docs already say 'normalized to supported command frontmatter'
- test_sync_removes_managed_commands exercises Cursor manifest mode
- test_frontmatter_normalized_to_supported_subset asserts non-whitelisted
fields (author, custom-field) are dropped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style: ruff format test_command_integrator.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(cursor-commands): warn on dropped frontmatter, traversal guards, target-aware diagnostics
Address all 10 required findings from APM Review Panel on PR #1046:
- Extract _make_package() to module-level test helper (DRY across 6 classes)
- Make integrate_command() target-aware via target_name kwarg; emit
'command arguments' instead of Claude-branded message for Cursor installs
- Detect frontmatter keys not preserved by the shared claude_command
transformer (author, mcp, parameters, ...) and surface diagnostics.warn()
per file so users see the lossy transform at install time
- Add install-time info note when .cursor/ is missing instead of silently
skipping deployment (improves CI-vs-dev discoverability)
- Validate base_name with validate_path_segments() and ensure_path_within()
before joining into commands_dir to close path-traversal vector via
malicious package filenames
- Harden find_files_by_glob() to reject hardlinks/files whose resolved path
escapes package_path (symlink check did not catch hardlinks)
- Surface a one-time IDE consent warning per package when Cursor commands
are deployed since they become invokable as IDE slash commands
- Add TODO(cursor-command-format) with tracking issue URL on both targets.py
and integrate_command() to make the shared-transformer debt trackable
- Add commands_cursor identity entry to _BUCKET_ALIASES for explicit
documentation parity with commands_opencode
- CHANGELOG entry under [Unreleased] and ide-tool-integration.md note
about the frontmatter subset limitation
- New TestCursorCommandPanelFindings regression class covers all five
panel-driven behaviours (dropped-key warn, traversal rejection,
target-aware diagnostics, IDE consent, skip note)
Verified: 6939 unit tests pass; ruff format + ruff check clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(install): gate cursor command deployment behind --allow-executable-commands
Address the 5 required findings from the APM Review Panel on PR #1046
(round 2) by replacing the hardcoded cursor branch and the warn()-only
notification with a unified consent gate.
Changes:
- targets.py: add TargetProfile.requires_executable_consent (default
False); set True on the cursor entry only. Documents the asymmetric-
consent risk for claude/opencode/gemini in the docstring (panel
finding #5: deferred to avoid breaking existing installs).
- command_integrator.py: introduce should_deploy_executable_commands()
helper. integrate_commands_for_target now accepts
allow_executable_commands and skips deployment with a warn-level
diagnostic naming the exact CLI flag when consent is required but
absent. Replaces the cursor-branded warn() block (Threat #8: warn
is notification, not consent).
- install.py: add --allow-executable-commands click option and field
on the local InstallContext; thread to _install_apm_dependencies
and through InstallRequest -> InstallService -> run_install_pipeline.
- services.py / pipeline.py / context.py / template.py / phases:
plumb allow_executable_commands through the install pipeline. The
dispatch loop only forwards the kwarg to the commands integrator.
Tests:
- New TestExecutableConsentGate class covering: helper short-circuit
for non-consent targets, helper block/allow on cursor, gated-skip
result + warning when flag absent, deploy when flag present, and
parametrized regression that claude/opencode/gemini are unaffected.
- Existing cursor deployment tests updated to pass
allow_executable_commands=True.
- test_install_context.py: include allow_executable_commands in the
expected fields tuple.
Docs:
- cli-commands.md and packages/apm-guide commands.md: document the new
flag and the cursor opt-in.
- CHANGELOG.md: reframe the cursor entry around the user-visible
opt-in flag and Threat #8 rationale.
Closes panel findings 1, 3, 4 from PR #1046 round 2. Finding 5
(asymmetric consent across slash-command targets) is documented in
the TargetProfile docstring as deferred -- flipping the flag for
claude/opencode/gemini would silently break existing installs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(install): address round 4 panel — IntegrateOptions, fail-closed default, --allow-cursor-commands
Round 4 panel raised 8 required findings against the cursor-commands
gate. Round 5 addresses all of them:
Architecture (python-architect)
- Add frozen IntegrateOptions dataclass with extra_kwargs(prim) helper
in install/services.py. Dispatch loop is now branchless — no more
inline 'if _prim_name == "commands"' special case.
- Hoist pkg_name once at top of integrate_commands_for_target.
- Move executable-commands gate before find_prompt_files in the happy
path; keep one find call inside the gate-skipped branch so the
user-facing skip warning includes an exact count.
CLI logging (cli-logging-expert)
- _post_install_summary surfaces the consent flag in --verbose output:
' allow-cursor-commands: true|false' (gated on logger.verbose).
- Add _should_emit_passthrough_notice helper + _passthrough_notified
instance set on CommandIntegrator. One-shot info diagnostic fires
once per (install, target) on Cursor installs explaining that
Cursor command files keep the Claude-compatible frontmatter subset
intentionally for cross-tool compatibility.
- Combine missing-.cursor/-dir hint with consent-flag mention so users
see both signals in one diagnostic.
DevX UX (devx-ux-expert)
- Rename CLI flag --allow-executable-commands -> --allow-cursor-commands
(target-scoped, user-friendly). Internal pipeline parameter remains
allow_executable_commands so future targets can share the gate
plumbing via their own --allow-<target>-commands flags. Mapping
happens at the CLI boundary in install().
- Update skip warning to name the new flag.
Supply-chain security (supply-chain-security-expert)
- Flip TargetProfile.requires_executable_consent default to True at
the dataclass level. New targets now refuse command deployment unless
an opt-out is explicit. Add explicit =False with rationale comment
on each existing target (claude, opencode, gemini), each referencing
enterprise/security.md.
- Add new '## Post-install code execution (Threat #8)' section to
docs/src/content/docs/enterprise/security.md with the per-target
posture matrix (cursor=gated, claude/opencode/gemini=opt-out),
rationale for the asymmetry, npm --ignore-scripts mental model,
and future-targets guidance.
- Remove dead bool() cast in should_deploy_executable_commands.
- Remove inline TODO comments referencing PR URL.
OSS growth (oss-growth-hacker)
- Rewrite CHANGELOG entry to lead with user outcome ('Cursor users
can install APM package slash commands with one flag…'). Security
rationale moved to end. Cursor 1.6+ lifecycle note included inline.
- Add Cursor 1.6+ lifecycle caveat to:
- docs/.../integrations/ide-tool-integration.md
- docs/.../reference/cli-commands.md
- packages/apm-guide/.apm/skills/apm-usage/commands.md
- packages/apm-guide/.apm/skills/apm-usage/package-authoring.md
Tests
- Update test assertions to use new flag name.
- Add requires_executable_consent=False to the synthetic test profile
in tests/unit/integration/test_data_driven_dispatch.py (consequence
of fail-closed default).
- Full unit suite: 6924 passed.
Round 5 panel: 6/6 specialists APPROVE; CEO synthesizer APPROVE.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(cursor): drop --allow-cursor-commands gate, deploy commands by default
Cursor slash commands have identical invocation semantics to
Claude/OpenCode/Gemini -- the user must type /cmdname to invoke. They
do not auto-execute on disk-write or IDE startup, so the npm
--ignore-scripts / Threat #8 framing was incorrect. The gate created
artificial asymmetry between targets with identical UX and violated
portable-by-manifest.
Removed:
- --allow-cursor-commands CLI flag
- TargetProfile.requires_executable_consent field
- should_deploy_executable_commands() helper + gate block
- IntegrateOptions per-primitive routing (only consumer was the gate)
- allow_executable_commands plumbing through 7 layers
- Threat #8 framing from docs + CHANGELOG
Kept:
- Cursor commands PrimitiveMapping (the actual feature)
- Lossy-frontmatter diagnostics.warn() per file (real value: surfaces
dropped Cursor-specific keys to package authors)
- Cursor 1.6+ lifecycle note (Cursor de-emphasizing commands)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(cursor-commands): act on PR #1046 panel review
Apply the actionable findings from the apm-review-panel critique on
PR #1046:
* docs: fix `.opencode/command/*.md` -> `.opencode/commands/*.md`
typo in the security guide (factual error in IDE integration map).
* CHANGELOG: rewrite Cursor entry user-first; lifecycle caveat moves
to a trailing sentence so the value prop reads first.
* integrator: gate the one-shot 'cross-tool compatibility' notice on
whether at least one file in the batch had dropped frontmatter
keys. Previously fired on every Cursor install -> noise on the
happy path. Cursor installs whose prompts only use the cross-tool
subset now emit zero notice. Other targets (claude, opencode)
unaffected.
* integrator: rewrite the dropped-keys warn message to drop internal
jargon ('shared command transformer'); use target-name framing
('not supported for cursor commands') and surface the canonical
kebab-case key list (no camelCase aliases).
* integrator: upgrade post-transform scan_text from WARN_POLICY to
BLOCK_POLICY. When a critical finding is detected (e.g. a hidden
tag char introduced by link resolution), skip the write and account
for it in result.files_skipped. Pre-install BLOCK gate already
scans source files, so this is defense-in-depth for the
transform-introduced edge case; no regression for legitimate
packages.
* base_integrator: actually close the hardlink containment gap in
find_files_by_glob. The prior is_relative_to() check did NOT
catch hardlinks -- a hardlink resolves to its own path inside the
package root. Add an st_nlink > 1 reject to prevent a malicious
package shipping a hardlink to (e.g.) /etc/passwd from being read
via integration. Update the inline comment to be accurate about
what each guard actually does.
Tests:
* tests/unit/integration/test_base_integrator.py: new
test_hardlink_escaping_package_root_is_excluded covering the
st_nlink reject; cleaned up F821 noqa via real pytest import.
* tests/unit/integration/test_command_integrator.py: 4 new tests in
TestCursorCommandPanelFindings:
- test_passthrough_notice_suppressed_on_clean_install
- test_passthrough_notice_emitted_when_any_file_drops_keys
- test_dropped_keys_warn_uses_user_facing_wording
- test_critical_security_finding_blocks_write
Updated the existing test_dropped_frontmatter_keys_warn assertion
to match the new wording.
* tests/integration/test_marketplace_plugin_integration.py: 2 new
integration tests locking in cursor 1.6+ command deployment via
the install pipeline (deploy when .cursor/ exists; respect opt-in
by NOT creating .cursor/ when missing).
Verification:
uv run --extra dev ruff check src/ tests/ -> All checks passed!
uv run --extra dev ruff format --check src/ tests/ -> 627 files OK
uv run --extra dev pytest tests/unit ... -> 7222 passed
Refs: #1046 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot-rework@github.com>
Co-authored-by: Daniel Meppiel <51440732+danielmeppiel@users.noreply.github.com>
Sign up for freeto 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

@richgo@danielmeppiel