-
Notifications
You must be signed in to change notification settings - Fork 940
Replace Playwright MCP server with Playwright CLI installation #14569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
930698a
Replace Playwright MCP server with Playwright CLI installation
6dffb0e
Pin Playwright CLI version range to 0.1.1
e91ce3a
Add npm provenance verification and break-glass config
abc4b83
Fix markdownlint: add language to fenced code block
274de79
Improve version resolution and provenance verification
ce6c598
Add tests for version pinning and default version range
aaf2908
Add E2E test for Playwright CLI installation via agent init
acc4286
Show status spinner during Playwright CLI installation
f1efa8d
Mirror playwright-cli skill files to all detected agent environments
54c4024
Update security design doc to match implementation
2deb6de
Verify workflow ref matches package version in provenance
8ffef67
Use callback-based workflow ref validation in provenance checker
79e6995
Replace npm provenance checking with Sigstore verification
95c68f8
Refactor SigstoreNpmProvenanceChecker for clarity
31157ef
Add libsodium to nuget-org package source mapping
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| # Safe npm Global Tool Installation | ||
|
|
||
| ## Overview | ||
|
|
||
| The Aspire CLI installs the `@playwright/cli` npm package as a global tool during `aspire agent init`. Because this tool runs with the user's full privileges, we must verify its authenticity and provenance before installation. This document describes the verification process, the threat model, and the reasoning behind each step. | ||
|
|
||
| ## Threat Model | ||
|
|
||
| ### What we're protecting against | ||
|
|
||
| 1. **Registry compromise** — An attacker gains write access to the npm registry and publishes a malicious version of `@playwright/cli` | ||
| 2. **Publish token theft** — An attacker steals a maintainer's npm publish token and publishes a tampered package | ||
| 3. **Man-in-the-middle** — An attacker intercepts the network request and substitutes a different tarball | ||
| 4. **Dependency confusion** — A malicious package with a similar name is installed instead of the intended one | ||
|
|
||
| ### What we're NOT protecting against | ||
|
|
||
| - Compromise of the legitimate source repository (`microsoft/playwright-cli`) itself | ||
| - Compromise of the GitHub Actions build infrastructure (Sigstore OIDC provider) | ||
| - Compromise of the Sigstore transparency log infrastructure | ||
| - Malicious code introduced through legitimate dependencies of `@playwright/cli` | ||
|
|
||
| ### Trust anchors | ||
|
|
||
| Our verification chain relies on these trust anchors: | ||
|
|
||
| | Trust anchor | What it provides | How it's protected | | ||
| |---|---|---| | ||
| | **npm registry** | Package metadata, tarball hosting | HTTPS/TLS, npm's infrastructure security | | ||
| | **Sigstore (Fulcio + Rekor)** | Cryptographic attestation signatures | Public CA with OIDC federation, append-only transparency log | | ||
| | **GitHub Actions OIDC** | Builder identity claims in Sigstore certificates | GitHub's infrastructure security | | ||
| | **Hardcoded expected values** | Package name, version range, expected source repository | Code review, our own release process | | ||
|
|
||
| ## Verification Process | ||
|
|
||
| ### Step 1: Resolve package version and metadata | ||
|
|
||
| **Action:** Run `npm view @playwright/cli@{versionRange} version` and `npm view @playwright/cli@{version} dist.integrity` to get the resolved version and the registry's SRI integrity hash. | ||
|
|
||
| **What this establishes:** We know the exact version we intend to install and the hash the registry claims for its tarball. | ||
|
|
||
| **Trust basis:** npm registry over HTTPS/TLS. | ||
|
|
||
| **Limitations:** If the registry is compromised, both the version and hash could be attacker-controlled. This step alone is insufficient — it only establishes what the registry *claims*. | ||
|
|
||
| ### Step 2: Check if already installed at a suitable version | ||
|
|
||
| **Action:** Run `playwright-cli --version` and compare against the resolved version. | ||
|
|
||
| **What this establishes:** Whether installation can be skipped entirely (already up-to-date or newer). | ||
|
|
||
| **Trust basis:** The previously-installed binary. If the user's system is compromised, this could be spoofed, but that's outside our threat model. | ||
|
|
||
| ### Step 3: Verify Sigstore attestations via npm | ||
|
|
||
| **Action:** | ||
| 1. Create a temporary directory with a minimal `package.json` | ||
| 2. Run `npm install @playwright/cli@{version} --ignore-scripts` to install the package from the registry as a project dependency | ||
| 3. Run `npm audit signatures` to verify Sigstore attestation signatures | ||
|
|
||
| **What this establishes:** That valid Sigstore-signed attestations exist for `@playwright/cli@{version}`. Specifically: | ||
|
|
||
| - The npm registry has attestation bundles for this package version | ||
| - The attestation signatures are cryptographically valid (signed by Sigstore's Fulcio CA) | ||
| - The attestation entries are present in the Rekor transparency log (inclusion proof verified) | ||
| - The OIDC identity in the signing certificate corresponds to a GitHub Actions workflow | ||
|
|
||
| **Trust basis:** Sigstore's public key infrastructure. Even if the npm registry is compromised, an attacker cannot forge valid Sigstore signatures — they would need to compromise Fulcio (the Sigstore CA) or obtain a valid OIDC token from GitHub Actions for the legitimate repository's workflow. | ||
|
|
||
| **Why a temporary project is needed:** `npm audit signatures` operates on installed project dependencies. It requires `node_modules` and a `package-lock.json` to know which packages to verify. For a global tool install there is no project context, so we create one temporarily. The package must be installed from the registry (not from a local tarball) because `npm audit signatures` skips packages with `resolved: file:...` in the lockfile. | ||
|
|
||
| **Limitations:** `npm audit signatures` verifies that *valid attestations exist* but does not expose the attestation *content*. It confirms "this package has authentic Sigstore-signed attestations" but does not tell us *what* those attestations say (e.g., which repository built the package). That's addressed in Step 4. | ||
|
|
||
| ### Step 4: Verify provenance source repository | ||
|
|
||
| **Action:** | ||
| 1. Fetch the attestation bundle from `https://registry.npmjs.org/-/npm/v1/attestations/@playwright/cli@{version}` | ||
| 2. Find the attestation with `predicateType: "https://slsa.dev/provenance/v1"` (SLSA Build L3 provenance) | ||
| 3. Base64-decode the DSSE envelope payload to extract the in-toto statement | ||
| 4. Parse `predicate.buildDefinition.externalParameters.workflow.repository` | ||
| 5. Verify it equals `https://github.com/microsoft/playwright-cli` | ||
|
|
||
| **What this establishes:** That the Sigstore-attested provenance for this package version claims it was built from the `microsoft/playwright-cli` GitHub repository via a GitHub Actions workflow. | ||
|
|
||
| **Trust basis:** The attestation content is protected by the Sigstore signature verified in Step 3. Since Step 3 confirmed the attestation is cryptographically authentic (signed by a valid Sigstore certificate corresponding to a GitHub Actions OIDC identity), the content we read in Step 4 cannot have been tampered with by the npm registry. An attacker would need to compromise Sigstore itself to forge attestation content pointing to `microsoft/playwright-cli`. | ||
|
|
||
| **Why we fetch from the registry API:** The npm CLI (`npm audit signatures`) verifies Sigstore signatures but does not expose provenance content in its output. The `--json` flag only produces `{"invalid":[],"missing":[]}`. There is no npm CLI command to read the source repository from a SLSA provenance attestation. We must fetch the attestation bundle directly from the registry API. | ||
|
|
||
| **Note on reading attested content from an untrusted source:** We are reading the attestation JSON from the same npm registry that could theoretically be compromised. However, the attestation *signature* was already verified by `npm audit signatures` in Step 3 using Sigstore's independent trust chain. We are relying on the fact that the registry serves the same attestation bundle that npm verified. If the registry served different attestation data to our HTTP request than what `npm audit signatures` verified, the provenance content could be spoofed. This is a residual risk — see "Residual Risks" below. | ||
|
|
||
| ### Step 5: Download and verify tarball integrity | ||
|
|
||
| **Action:** | ||
| 1. Run `npm pack @playwright/cli@{version}` to download the tarball | ||
| 2. Compute SHA-512 hash of the downloaded tarball | ||
| 3. Compare against the SRI integrity hash obtained in Step 1 | ||
|
|
||
| **What this establishes:** That the tarball we have on disk is bit-for-bit identical to what the npm registry published for this version. | ||
|
|
||
| **Trust basis:** Cryptographic hash comparison (SHA-512). If the hash matches, the content is the same regardless of how it was delivered. | ||
|
|
||
| **Relationship to Step 3:** The Sigstore attestations verified in Step 3 are bound to the package version and its published content. The integrity hash in the registry packument is the canonical identifier for the tarball content. By verifying our tarball matches this hash, we establish that our tarball is the same artifact that the Sigstore attestations cover. | ||
|
|
||
| ### Step 6: Install globally from verified tarball | ||
|
|
||
| **Action:** Run `npm install -g {tarballPath}` to install the verified tarball as a global tool. | ||
|
|
||
| **What this establishes:** The tool is installed and available on the user's PATH. | ||
|
|
||
| **Trust basis:** All preceding verification steps have passed. The tarball content has been verified against the registry's published hash (Step 5), the Sigstore attestations for that content are cryptographically valid (Step 3), and the attestations claim the correct source repository (Step 4). | ||
|
|
||
| ### Step 7: Generate skill files | ||
|
|
||
| **Action:** Run `playwright-cli install --skills` to generate agent skill files. | ||
|
|
||
| **What this establishes:** The Playwright CLI skill files are available for agent environments. | ||
|
|
||
| ## Verification Chain Summary | ||
|
|
||
| ```text | ||
| │ Hardcoded expectations │ | ||
| │ • Package: @playwright/cli │ | ||
| │ • Version range: 0.1.1 │ | ||
| │ • Source: microsoft/ │ | ||
| │ playwright-cli │ | ||
| └──────────────┬────────────────┘ | ||
| │ | ||
| ┌──────────────▼────────────────┐ | ||
| │ Step 1: Resolve version + │ | ||
| │ integrity hash from registry │ | ||
| └──────────────┬────────────────┘ | ||
| │ | ||
| ┌────────────────────┼────────────────────┐ | ||
| │ │ │ | ||
| ┌──────────▼──────────┐ ┌─────▼──────────┐ ┌──────▼──────────┐ | ||
| │ Step 3: npm audit │ │ Step 4: Verify │ │ Step 5: npm pack│ | ||
| │ signatures │ │ provenance repo │ │ + SHA-512 check │ | ||
| │ (Sigstore crypto) │ │ (attestation │ │ (tarball │ | ||
| │ │ │ content) │ │ integrity) │ | ||
| └──────────┬───────────┘ └─────┬──────────┘ └──────┬──────────┘ | ||
| │ │ │ | ||
| │ Attestation is │ Built from │ Tarball matches | ||
| │ authentic │ expected repo │ published hash | ||
| └────────────────────┼─────────────────────┘ | ||
| │ | ||
| ┌──────────────▼────────────────┐ | ||
| │ Step 6: npm install -g │ | ||
| │ (from verified tarball) │ | ||
| └───────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Residual Risks | ||
|
|
||
| ### 1. Registry serving different attestation data to different clients | ||
|
|
||
| **Risk:** The npm registry could theoretically serve one attestation bundle to `npm audit signatures` (which passes verification) and a different bundle to our HTTP API request (with spoofed provenance content). | ||
|
|
||
| **Mitigation:** This would require active, targeted compromise of the npm registry's serving infrastructure — not just a publish token theft or package tampering. The Rekor transparency log provides a public record of all attestations, making such targeted serving detectable. | ||
|
|
||
| **Alternative mitigation:** We could eliminate this risk entirely by parsing the attestation bundle ourselves and verifying the Sigstore signature directly in C#. This would require implementing ECDSA signature verification, X.509 certificate chain validation, and Merkle inclusion proof verification. This significantly increases implementation complexity and is not recommended for the initial implementation. | ||
|
|
||
| ### 2. Time-of-check-to-time-of-use (TOCTOU) | ||
|
|
||
| **Risk:** The package could be replaced on the registry between our verification steps and the global install. | ||
|
|
||
| **Mitigation:** We verify the SHA-512 hash of the tarball we actually install (Step 5), and we install from the local tarball file (not from the registry again). The verified tarball is the same file that gets installed. | ||
|
|
||
| ### 3. Transitive dependency attacks | ||
|
|
||
| **Risk:** `@playwright/cli` has dependencies that could be compromised. | ||
|
|
||
| **Mitigation:** The `--ignore-scripts` flag prevents execution of install scripts. However, the dependencies' code runs when the tool is invoked. This is partially mitigated by Sigstore attestations covering the dependency tree, but comprehensive supply chain verification of all transitive dependencies is out of scope. | ||
|
|
||
| ## Implementation Constants | ||
|
|
||
| ```csharp | ||
| internal const string PackageName = "@playwright/cli"; | ||
| internal const string VersionRange = "0.1.1"; | ||
| internal const string ExpectedSourceRepository = "https://github.com/microsoft/playwright-cli"; | ||
| internal const string NpmRegistryAttestationsUrl = "https://registry.npmjs.org/-/npm/v1/attestations"; | ||
| internal const string SlsaProvenancePredicateType = "https://slsa.dev/provenance/v1"; | ||
| ``` | ||
|
|
||
| ## Future Improvements | ||
|
|
||
| 1. **Direct Sigstore verification in C#** — Eliminate the dependency on `npm audit signatures` by implementing Sigstore bundle verification natively. This would remove residual risk #1 and reduce the dependency on npm CLI. | ||
| 2. **Rekor log verification** — Independently verify the Rekor inclusion proof to confirm the attestation was logged in the public transparency log. | ||
| 3. **Certificate identity verification** — Check the OIDC identity claims in the Sigstore signing certificate (issuer, subject, workflow reference) rather than just the provenance payload. | ||
| 4. **Pinned tarball hash** — Ship a known-good SRI hash with each Aspire release, eliminating the need to trust the registry for the hash at all. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mitchdenny update this to include the other metadata we verify, e.g. tag-based release, workflow name, etc?