Skip to content

security: require opt-in for automatic npm restore to avoid build-time script execution#54

Open
ANcpLua wants to merge 1 commit intomainfrom
dev/propose-fix-for-build-time-rce-vulnerability
Open

security: require opt-in for automatic npm restore to avoid build-time script execution#54
ANcpLua wants to merge 1 commit intomainfrom
dev/propose-fix-for-build-time-rce-vulnerability

Conversation

@ANcpLua
Copy link
Owner

@ANcpLua ANcpLua commented Mar 22, 2026

Motivation

  • The SDK previously auto-detected any repository package.json and ran npm install/npm ci during Restore, which executes npm lifecycle scripts and creates a build-time RCE risk.
  • The goal is to make automatic npm restore explicit so untrusted repositories or PRs cannot trigger npm lifecycle scripts by simply adding a package.json file.

Description

  • Change src/Build/Common/Npm.targets so the repository package.json is only included when EnableDefaultNpmPackageFile is explicitly set to true instead of being picked up by default.
  • Update existing npm-related integration tests in tests/ANcpLua.Sdk.Tests/SdkTests.cs to opt-in by calling .WithProperty("EnableDefaultNpmPackageFile", "true") where the tests require automatic npm behavior.
  • Add a regression test NpmRestore_DefaultAutoDetectionDisabled that verifies the secure default (no package-lock.json and no npm stamp file created) when EnableDefaultNpmPackageFile is not set.

Testing

  • An automated dotnet test invocation was attempted: dotnet test tests/ANcpLua.Sdk.Tests/ANcpLua.Sdk.Tests.csproj --filter "NpmInstall|NpmRestore|Npm_Dotnet_Build_sln|Npm_Dotnet_sln|NpmRestore_MultipleFiles|Npm_Dotnet_Build_RestoreLockedMode_Fail|Npm_Dotnet_Build_Ci_Success", but it failed in this environment because dotnet is not installed.
  • Static verification was performed by running git diff and inspecting the modified files to ensure the condition and test updates are correct.
  • The changes were committed (fix: require opt-in for automatic npm restore) to the branch so CI can run the updated test suite in an environment with dotnet available.

Codex Task

Copilot AI review requested due to automatic review settings March 22, 2026 15:17
@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

Summary by CodeRabbit

  • Tests

    • Updated npm-related test suite to cover default package file generation scenarios and auto-detection control.
  • Chores

    • Modified npm package file detection logic in build system; npm package file inclusion now follows updated conditions.

Walkthrough

The changes modify npm package file auto-detection in MSBuild by requiring an explicit EnableDefaultNpmPackageFile property set to 'true' instead of implicit detection when the property is unset. Tests are updated to explicitly enable this feature where needed and to verify that auto-detection can be disabled.

Changes

Cohort / File(s) Summary
NPM Auto-Detection Configuration
src/Build/Common/Npm.targets
Changed NpmPackageFile inclusion condition to require explicit EnableDefaultNpmPackageFile='true' and package.json existence, replacing implicit inclusion when property was empty. This shifts from opt-out to opt-in behavior for default npm package file inclusion.
Test Updates for NPM Configuration
tests/ANcpLua.Sdk.Tests/SdkTests.cs
Updated six npm-related test methods to explicitly set EnableDefaultNpmPackageFile property. Renamed Npm_Dotnet_Build_sln to NpmRestore_DefaultAutoDetectionDisabled with changed test logic to verify restore succeeds while confirming package-lock.json and .npm-install-stamp are not created when auto-detection is disabled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Suggested labels

area:infra


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Title check ❌ Error Title uses 'security:' prefix instead of required conventional commit area (feat|fix|refactor|chore|docs|test|ci|perf) and exceeds 72-character limit at 87 characters. Use conventional commit format like 'fix(npm): require opt-in for automatic restore' and keep under 72 characters total.
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed Description clearly explains the security motivation, technical changes to Npm.targets and tests, and test strategy including the new regression test.
Otel Instrumentation Required ✅ Passed No new injectable service classes introduced; changes limited to MSBuild configuration and test updates.
No Unbounded Mcp Responses ✅ Passed PR modifies only non-MCP tool files; no changes to src/qyl.mcp/ detected.
Duckdb Backpressure On Write Paths ✅ Passed PR modifies MSBuild npm package handling to require explicit opt-in for automatic npm restore, mitigating build-time script execution risks with zero DuckDB references.
Cancellationtoken Threading ✅ Passed PR contains no new public async methods in C# source files under src/; only project files, NuSpec files, and an MSBuild XML targets file.

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of the SDK by changing the default behavior of package.json detection. Previously, the SDK would automatically process any package.json found, which posed a build-time Remote Code Execution (RCE) risk. The updated approach now mandates an explicit opt-in via a property, ensuring that npm lifecycle scripts are only executed when intended, thereby preventing malicious code from running from untrusted sources during the build process.

Highlights

  • Security Enhancement: Changed the SDK's npm integration to require explicit opt-in for automatic package.json detection, mitigating build-time RCE risks by preventing untrusted repositories from executing npm lifecycle scripts.
  • Configuration Update: Modified src/Build/Common/Npm.targets to only include package.json for npm operations when the EnableDefaultNpmPackageFile property is explicitly set to true.
  • Test Suite Adjustment: Updated existing npm-related integration tests in SdkTests.cs to explicitly set EnableDefaultNpmPackageFile to true where automatic npm behavior is expected.
  • New Regression Test: Added a new test, NpmRestore_DefaultAutoDetectionDisabled, to verify that automatic npm restore is indeed disabled by default when the opt-in property is not set.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a security concern by making automatic npm restore an opt-in feature. The change in Npm.targets is correct, and the updates to existing tests to opt-in are necessary. The addition of the NpmRestore_DefaultAutoDetectionDisabled test is excellent as it verifies the new secure-by-default behavior. I've included one suggestion to improve the maintainability of the test suite by reducing code duplication.

Comment on lines +1005 to +1006
await using var project = CreateProject(SdkWebName)
.WithProperty("EnableDefaultNpmPackageFile", "true");

Choose a reason for hiding this comment

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

medium

To improve maintainability and reduce code duplication, consider creating a helper method for setting up projects that require npm. This pattern of CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true") is repeated across multiple tests (NpmRestore, Npm_Dotnet_Build_sln, Npm_Dotnet_sln, Npm_Dotnet_Build_RestoreLockedMode_Fail, and Npm_Dotnet_Build_Ci_Success).

You could add a private helper method to the SdkTests class like this:

private SdkProjectBuilder CreateProjectWithNpmEnabled() =>
    CreateProject(SdkWebName)
        .WithProperty("EnableDefaultNpmPackageFile", "true");

Then, you can simplify the test setup.

        await using var project = CreateProjectWithNpmEnabled();

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the SDK’s npm restore behavior by disabling implicit package.json auto-detection, requiring an explicit opt-in to avoid unintended npm lifecycle script execution during build/restore.

Changes:

  • Require EnableDefaultNpmPackageFile=true for the SDK to auto-include $(MSBuildProjectDirectory)/package.json as an NpmPackageFile.
  • Update npm-related integration tests to explicitly opt in where automatic npm behavior is expected.
  • Add a regression test ensuring the secure default: no npm restore side-effects occur when the property is not set.

Reviewed changes

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

File Description
tests/ANcpLua.Sdk.Tests/SdkTests.cs Opt-in existing npm tests via EnableDefaultNpmPackageFile=true and add a regression test for the secure default behavior.
src/Build/Common/Npm.targets Gate default package.json detection behind EnableDefaultNpmPackageFile == 'true'.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a83627b9da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


<ItemGroup>
<NpmPackageFile Condition="'$(EnableDefaultNpmPackageFile)' == '' AND Exists('$(MSBuildProjectDirectory)/package.json')" Include="$(MSBuildProjectDirectory)/package.json"/>
<NpmPackageFile Condition="'$(EnableDefaultNpmPackageFile)' == 'true' AND Exists('$(MSBuildProjectDirectory)/package.json')" Include="$(MSBuildProjectDirectory)/package.json"/>

Choose a reason for hiding this comment

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

P1 Badge Allow project-level opt-in with Directory.Build.props imports

This condition is evaluated when Npm.targets is imported, so the new opt-in only works if EnableDefaultNpmPackageFile is already set before that import. In the repo's supported SdkElementDirectoryBuildProps mode, the SDK is injected from Directory.Build.props (tests/ANcpLua.Sdk.Tests/Helpers/SdkProjectBuilder.cs:329-346) and project properties are emitted later in the generated .csproj (:357-382), so .WithProperty("EnableDefaultNpmPackageFile", "true") never repopulates @(NpmPackageFile). That leaves the Sdk100DirectoryBuildPropsTests npm cases still broken and means real projects using the Directory.Build.props import style cannot re-enable the default package.json behavior from their project file.

Useful? React with 👍 / 👎.


<ItemGroup>
<NpmPackageFile Condition="'$(EnableDefaultNpmPackageFile)' == '' AND Exists('$(MSBuildProjectDirectory)/package.json')" Include="$(MSBuildProjectDirectory)/package.json"/>
<NpmPackageFile Condition="'$(EnableDefaultNpmPackageFile)' == 'true' AND Exists('$(MSBuildProjectDirectory)/package.json')" Include="$(MSBuildProjectDirectory)/package.json"/>

Choose a reason for hiding this comment

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

P2 Badge Keep explicit /t:NpmInstall working without the new opt-in

Gating the default package.json item here also empties @(NpmPackageFile) for the manual NpmInstall target (src/Build/Common/Npm.targets:43-45). Any existing CI or local workflow that explicitly runs dotnet msbuild /t:NpmInstall in a web project will no longer see the repo's package.json, so npm dependencies stop being installed unless the caller also knows to set EnableDefaultNpmPackageFile; the security concern in this change is about automatic restore/build entry points, not an explicitly requested npm install.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/ANcpLua.Sdk.Tests/SdkTests.cs`:
- Around line 1005-1006: Extract the repeated project setup into a small helper
method (e.g., CreateProjectWithDefaultNpmOptIn) that wraps
CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true");
update all callers (tests currently calling
CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true") —
references around SdkTests.cs lines near the CreateProject usage such as the
blocks using CreateProject and SdkWebName) to call the new helper instead; keep
the helper signature similar to CreateProject so it can be used in places that
currently chain additional calls, and run tests to ensure no behavior changed.
- Line 1082: The test method Npm_Dotnet_Build_sln does not follow the
MethodName_Scenario_ExpectedResult convention; rename the method to a
descriptive name that follows that pattern (for example,
BuildSolution_WithNpmAndDotnet_ReturnsSuccess or
BuildSolution_WhenUsingNpmAndDotnet_Succeeds) so the test method (originally
Npm_Dotnet_Build_sln) clearly describes the method under test, the scenario, and
the expected outcome.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7d6e8d6-e5b4-4ad4-9fa8-e2b4af00d0de

📥 Commits

Reviewing files that changed from the base of the PR and between 8d35cc8 and a83627b.

📒 Files selected for processing (2)
  • src/Build/Common/Npm.targets
  • tests/ANcpLua.Sdk.Tests/SdkTests.cs
📜 Review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (windows-latest)
  • GitHub Check: test (ubuntu-latest)
🧰 Additional context used
📓 Path-based instructions (1)
tests/**/*.cs

⚙️ CodeRabbit configuration file

xUnit.v3 test projects using xunit.v3.mtp-v2 with AwesomeAssertions and NSubstitute. Follow Arrange-Act-Assert pattern. Use descriptive test names: MethodName_Scenario_ExpectedResult. Test async methods with async Task, not async void. Flag hardcoded test data that should come from the seeded DuckDB file (the single source of truth for demo/test data). Prefer [Theory] with [InlineData] or [MemberData] over duplicated [Fact] methods testing variations of the same behavior. Flag tests that depend on test execution order.

Files:

  • tests/ANcpLua.Sdk.Tests/SdkTests.cs
🔇 Additional comments (2)
src/Build/Common/Npm.targets (1)

4-4: Opt-in gate is correctly enforced for default package.json discovery.

Line 4 now requires explicit EnableDefaultNpmPackageFile=true before default npm restore wiring is activated, which closes the implicit restore/script-execution path.

tests/ANcpLua.Sdk.Tests/SdkTests.cs (1)

1057-1079: Regression test covers the secure default behavior correctly.

This test validates the intended default: restore succeeds without generating package-lock.json or .npm-install-stamp when opt-in is not set.

Comment on lines +1005 to +1006
await using var project = CreateProject(SdkWebName)
.WithProperty("EnableDefaultNpmPackageFile", "true");
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Extract repeated npm opt-in setup into a helper to avoid drift.

The same CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true") chain is repeated across tests; centralize it in one helper.

Refactor sketch
+    private SdkProjectBuilder CreateWebProjectWithDefaultNpmEnabled() =>
+        CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true");

-        await using var project = CreateProject(SdkWebName)
-            .WithProperty("EnableDefaultNpmPackageFile", "true");
+        await using var project = CreateWebProjectWithDefaultNpmEnabled();

Also applies to: 1033-1034, 1084-1085, 1118-1119, 1202-1203, 1228-1229

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/ANcpLua.Sdk.Tests/SdkTests.cs` around lines 1005 - 1006, Extract the
repeated project setup into a small helper method (e.g.,
CreateProjectWithDefaultNpmOptIn) that wraps
CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true");
update all callers (tests currently calling
CreateProject(SdkWebName).WithProperty("EnableDefaultNpmPackageFile", "true") —
references around SdkTests.cs lines near the CreateProject usage such as the
blocks using CreateProject and SdkWebName) to call the new helper instead; keep
the helper signature similar to CreateProject so it can be used in places that
currently chain additional calls, and run tests to ensure no behavior changed.

}

[Fact]
public async Task Npm_Dotnet_Build_sln()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Rename this test to the required scenario/expected naming format.

Line 1082 (Npm_Dotnet_Build_sln) is not in MethodName_Scenario_ExpectedResult format.

Suggested rename
-    public async Task Npm_Dotnet_Build_sln()
+    public async Task NpmRestore_DotnetBuildSln_WithDefaultNpmPackageFileEnabled_CreatesNpmArtifacts()

As per coding guidelines, "Use descriptive test names: MethodName_Scenario_ExpectedResult."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task Npm_Dotnet_Build_sln()
public async Task NpmRestore_DotnetBuildSln_WithDefaultNpmPackageFileEnabled_CreatesNpmArtifacts()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/ANcpLua.Sdk.Tests/SdkTests.cs` at line 1082, The test method
Npm_Dotnet_Build_sln does not follow the MethodName_Scenario_ExpectedResult
convention; rename the method to a descriptive name that follows that pattern
(for example, BuildSolution_WithNpmAndDotnet_ReturnsSuccess or
BuildSolution_WhenUsingNpmAndDotnet_Succeeds) so the test method (originally
Npm_Dotnet_Build_sln) clearly describes the method under test, the scenario, and
the expected outcome.

@ANcpLua ANcpLua self-assigned this Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants