Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
897e15a
fix(alias): reject path-traversing dependency aliases
Danvs60 Sep 7, 2026
38608f4
fix(alias): surface friendly error for traversal aliases
Danvs60 Sep 8, 2026
206d489
test(alias): prove install-phase symlink escape is blocked
Danvs60 Sep 8, 2026
9da1ecd
test(alias): trap ensure_path_within guards end-to-end at install tier
Danvs60 Sep 8, 2026
e845ebf
Refactor install path handling for dependencies
Danvs60 Sep 8, 2026
e2015db
fix(alias): centralize safe destinations without restricting sources
danielmeppiel Sep 8, 2026
1c9029a
Merge contributor follow-up while retaining canonical alias paths
danielmeppiel Sep 8, 2026
2893043
Merge main before alias compatibility validation
danielmeppiel Sep 8, 2026
3bad638
fix(alias): preserve normalized roots and legacy cache preflight
danielmeppiel Sep 8, 2026
ae3099d
fix(alias): preserve remote source anchors and cache replacement
danielmeppiel Sep 8, 2026
9167a99
fix(alias): preserve placement and current ref replay after install
danielmeppiel Sep 9, 2026
c5b4a86
test(cache): exercise stale bare fallback under corrupt receipts
danielmeppiel Sep 9, 2026
58190d8
test(lockfile): declare absent alias in dev dependency fixtures
danielmeppiel Sep 9, 2026
d1dd63c
Merge current main into alias recovery candidate
danielmeppiel Sep 9, 2026
5a0d872
fix(spec): preserve published alias schema identities
danielmeppiel Sep 9, 2026
806cd27
Merge current main into PR #2901 for integration qualification
danielmeppiel Sep 17, 2026
899397a
fix(alias): clarify compatibility and recovery after main integration
danielmeppiel Sep 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .apm/architecture/owners/contracts-tooling.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
{
"id": "dependency-identity-materialization",
"decision": "Dependency comparison identity and policy casing vs display-cased materialization path; embedded git URL subpath validation",
"owner": "models/dependency/identity.py + materialization.py + DependencyReference (_check_no_embedded_subpath consumes core/host_providers.py)",
"owner": "models/dependency/identity.py + materialization.py + object_fields.py (alias validation) + deps/apm_resolver.py (remote source-coordinate anchoring) + DependencyReference (_check_no_embedded_subpath consumes core/host_providers.py)",
"selectors": [
"src/apm_cli/models/dependency/identity.py",
"src/apm_cli/models/dependency/materialization.py",
"src/apm_cli/models/dependency/reference.py"
"src/apm_cli/models/dependency/object_fields.py",
"src/apm_cli/models/dependency/reference.py",
"src/apm_cli/deps/apm_resolver.py"
],
"guards": ["contracts-tooling-dependency-identity", "contracts-tooling-policy-identity"]
},
Expand Down Expand Up @@ -74,7 +76,7 @@
{
"id": "read-only-lockfile-path",
"decision": "Lockfile read path, timestamp emission, and reproducible fallback",
"owner": "deps/lockfile.py (resolve_lockfile_path_for_read, LockFile.write, resolve_reproducible_timestamp)",
"owner": "deps/lockfile.py (resolve_lockfile_path_for_read, LockFile.write, resolve_reproducible_timestamp, LockedDependency alias projection)",
"selectors": ["src/apm_cli/deps/lockfile.py"],
"guards": [
"contracts-tooling-lockfile-read",
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- PR-review scheduler no longer queues every open pull request. A fresh review requires the `panel-review` label (same trigger as the Agentic Workflow), `status/accepted` on the PR, or an explicit named PR list. The reviewing session also requires `status/accepted` on the PR or a linked issue; otherwise scheduler and review-worker stop with no comment. The worker may clear `panel-review`; the scheduler does not comment or change labels. Both also apply a CODEOWNERS last-comment gate: read the last CODEOWNER comment as conditions and evaluate them against later comments AND labels on the PR and linked issues. Drop or `noop` only when those conditions are unmet or unclear. Named list does not bypass that gate.
- Issue-triage sweep no longer classifies real GitHub bug forms as spam: heading/list line matches no longer swallow the rest of the body after markup strip.

### Security

- **BREAKING (invalid inputs):** Reject bare `.`/`..` aliases and unsafe symlink destinations; safe dotted aliases and CLI commands/flags are unchanged. Preserve replay placement via optional lock `alias`, without a `lockfile_version` bump. Clients lacking 0.1.41 manifest `$schema` support fail closed on that explicit opt-in; see `specs/openapm-v0.1.md` (`req-mf-025`) and [migration](https://microsoft.github.io/apm/troubleshooting/migration/#rejected-dependency-aliases). - by @Danvs60 (#2901)

## [0.31.0] - 2026-09-15

### Added
Expand All @@ -61,6 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

- The shared gh-aw APM pack job now declares `contents: read` (previously `permissions: {}`), the minimum the explicit built-in-token path needs. No write scope is added, and the token is not forwarded to restore or agent jobs. (#2706)
- Dependency policy `allow`, `deny`, and exact `require` matching now follows canonical owner/repository casing, fixing mixed-case blocks and deny fail-open behavior while retaining lazy shared required-package lookup. APM 0.30.0 and earlier match patterns byte-exactly against the lowercased identity; lowercase patterns keep matching in every release, so drop workaround duplicates only after every runner uses a release carrying this fix. (#2706)
- File-lock retry diagnostics no longer include paths or exception text that may contain secrets, while retaining retry counts and delays. (#2742)

## [0.30.0] - 2026-09-07
Expand Down
59 changes: 57 additions & 2 deletions CONFORMANCE.json
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,61 @@
"tests/spec_conformance/test_manifest_reqs.py::test_consumer_preserves_registry_identity_on_structured_rewrite"
]
},
{
"conformance_class": "consumer",
"id": "req-mf-025",
"keyword": "MUST",
"section": "4.3.2",
"status": "active",
"test_count": 45,
"tests": [
"tests/spec_conformance/test_alias_reqs.py::test_absent_alias_is_not_inferred_from_inventory_name",
"tests/spec_conformance/test_alias_reqs.py::test_alias_rejects_symlink_destination[outside]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_rejects_symlink_destination[root]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[ \\r..\\t-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[ \\tMy.Safe\\n-True-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[ \\t\\n-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[.-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[..-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[../escape-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[..\\n-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[.safe-True-True]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[1-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[foo..bar-True-True]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[my-skill.v2-True-True]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[safe.-True-True]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[safe/name-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[safe\\n-True-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_schemas_distinguish_input_from_canonical_output[safe\\ninside-False-False]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[git- \\tMy.Safe\\n]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[git-.safe]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[git-None]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[git-foo..bar]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[git-my-skill.v2]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[git-safe.]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[local- \\tMy.Safe\\n]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[local-.safe]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[local-None]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[local-foo..bar]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[local-my-skill.v2]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[local-safe.]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[registry- \\tMy.Safe\\n]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[registry-.safe]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[registry-None]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[registry-foo..bar]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[registry-my-skill.v2]",
"tests/spec_conformance/test_alias_reqs.py::test_alias_survives_lock_replay_without_changing_source[registry-safe.]",
"tests/spec_conformance/test_alias_reqs.py::test_invalid_lock_alias_guidance_repairs_the_lock_not_the_manifest",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[%2e%2e]",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[../escape]",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[..]",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[.]",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[1]",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[]",
"tests/spec_conformance/test_alias_reqs.py::test_lock_alias_rejects_invalid_destination_names[foo/../bar]"
]
},
{
"conformance_class": "governance",
"id": "req-pl-001",
Expand Down Expand Up @@ -1433,7 +1488,7 @@
"spec_version": "v0.1.1",
"summary_by_class": {
"consumer": {
"active": 90,
"active": 91,
"skipped": 1,
"unbound": 0,
"xfail": 0
Expand All @@ -1457,5 +1512,5 @@
"xfail": 0
}
},
"total_requirements": 122
"total_requirements": 123
}
3 changes: 2 additions & 1 deletion CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Repository-coordinate segments are case-insensitive for `github.com`, GitHub Ent
| Class | Active | Skipped | Xfail | Unbound |
|-------|-------:|--------:|------:|--------:|
| Producer | 12 | 0 | 0 | 0 |
| Consumer | 90 | 1 | 0 | 0 |
| Consumer | 91 | 1 | 0 | 0 |
| Registry | 1 | 0 | 0 | 0 |
| Governance | 18 | 0 | 0 | 0 |

Expand Down Expand Up @@ -87,6 +87,7 @@ Repository-coordinate segments are case-insensitive for `github.com`, GitHub Ent
| [req-mf-022](docs/src/content/docs/specs/openapm-v0.1.md#req-mf-022) | MUST | 4.3.2 | consumer | active | 2 | - |
| [req-mf-023](docs/src/content/docs/specs/openapm-v0.1.md#req-mf-023) | MUST | 4.5 | consumer | active | 1 | - |
| [req-mf-024](docs/src/content/docs/specs/openapm-v0.1.md#req-mf-024) | MUST | 4.3.2 | consumer | active | 1 | - |
| [req-mf-025](docs/src/content/docs/specs/openapm-v0.1.md#req-mf-025) | MUST | 4.3.2 | consumer | active | 45 | - |
| [req-pl-001](docs/src/content/docs/specs/openapm-v0.1.md#req-pl-001) | MUST | 6.1 | governance | active | 1 | - |
| [req-pl-002](docs/src/content/docs/specs/openapm-v0.1.md#req-pl-002) | MUST | 6.2 | governance | active | 1 | - |
| [req-pl-003](docs/src/content/docs/specs/openapm-v0.1.md#req-pl-003) | MUST | 6.4 | governance | active | 1 | - |
Expand Down
4 changes: 4 additions & 0 deletions docs/public/specs/manifests/openapm-v0.1.requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ requirements:
keyword: MUST
section: "4.3.2"
conformance_class: consumer
- id: req-mf-025
keyword: MUST
section: "4.3.2"
conformance_class: consumer
- id: req-ext-001
keyword: MUST
section: "4.1"
Expand Down
86 changes: 86 additions & 0 deletions docs/public/specs/schemas/lockfile-v0.1.41.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://microsoft.github.io/apm/specs/schemas/lockfile-v0.1.41.schema.json",
"title": "OpenAPM Lockfile (apm.lock.yaml) v0.1, schema revision 0.1.41",
"description": "Normative JSON Schema (Draft 2020-12) for the OpenAPM v0.1 lockfile. Where this schema and the prose of OpenAPM v0.1 disagree, the prose is authoritative.",
"type": "object",
"required": ["lockfile_version", "dependencies"],
"properties": {
"lockfile_version": { "enum": ["1", "2"] },
"generated_at": { "type": "string" },
"apm_version": { "type": "string" },
"dependencies": { "type": "array", "items": { "$ref": "#/$defs/entry" } },
"mcp_servers": { "type": "array" },
"mcp_configs": { "type": "object" },
"local_deployed_files": { "type": "array", "items": { "type": "string" } },
"local_deployed_file_hashes": {
"type": "object",
"additionalProperties": {
"$comment": "Bare-hex form retained for v0.1 reader-tolerance per req-lk-016; v0.2 will require the sha256: envelope exclusively.",
"type": "string",
"pattern": "^(sha256:[0-9a-f]{64}|sha384:[0-9a-f]{96}|sha512:[0-9a-f]{128}|[0-9a-f]{64})$"
}
},
"attestations": { "type": "object", "description": "Reserved for v0.2 (publisher provenance). Readers MUST preserve byte-equivalent on round-trip." }
},
"patternProperties": {
"^x-[a-z][a-z0-9-]*$": {}
},
"additionalProperties": true,
"$defs": {
"hashEnvelope": {
"type": "string",
"pattern": "^(sha256:[0-9a-f]{64}|sha384:[0-9a-f]{96}|sha512:[0-9a-f]{128}|[0-9a-f]{64})$"
},
"entry": {
"type": "object",
"properties": {
"repo_url": { "type": "string", "minLength": 1 },
"alias": {
"type": "string",
"pattern": "^[a-zA-Z0-9._-]+(?![\\s\\S])",
"not": { "enum": [".", ".."] },
"description": "Validated install-directory override, not source identity; req-mf-025."
},
"materialization_repo_url": {
"type": "string",
"minLength": 1,
"description": "Source-cased repository identifier for materialization; non-identity metadata per req-lk-022.",
"$comment": "MUST map to the same canonical identity as repo_url under req-rs-016."
},
"host": { "type": "string" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"registry_prefix": { "type": "string" },
"resolved_ref": { "type": "string" },
"resolved_commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
"tree_sha256": { "$ref": "#/$defs/hashEnvelope" },
"version": { "type": "string" },
"virtual_path": { "type": "string" },
"is_virtual": { "type": "boolean" },
"depth": { "type": "integer", "minimum": 0 },
"resolved_by": { "type": "string" },
"package_type": { "type": "string" },
"skill_subset": { "type": "array", "items": { "type": "string" } },
"deployed_files": { "type": "array", "items": { "type": "string" } },
"deployed_file_hashes": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/hashEnvelope" }
},
"source": { "enum": ["local", "registry"] },
"resolved_url": { "type": "string" },
"resolved_hash": { "$ref": "#/$defs/hashEnvelope" },
"local_path": { "type": "string" },
"content_hash": { "$ref": "#/$defs/hashEnvelope" },
"is_dev": { "type": "boolean" },
"constraint": { "type": "string" },
"resolved_tag": { "type": "string" },
"resolved_at": { "type": "string" },
"name": { "type": "string" },
"declared_license": { "type": "string" },
"attestations": { "type": "object" }
},
"patternProperties": { "^x-[a-z][a-z0-9-]*$": {} },
"additionalProperties": true
}
}
}
Loading
Loading