Most applications depend on packages maintained by other developers. A .NET project might use NuGet packages, a JavaScript application might depend on npm packages, and a Python service might install libraries from PyPI. These dependencies save development time, but they also introduce security and maintenance risks.
A package may contain a known vulnerability, come from an untrusted publisher, or introduce an unexpected dependency into your application. In some cases, an attacker can publish a malicious package with a name similar to a popular library and wait for developers to install it.
This is where package access controls become useful.
A package firewall is a security control that restricts which package sources, publishers, or dependencies developers and automated build systems can access. GitHub provides several features that help organizations manage package use, including dependency review, Dependabot, repository rules, and organization-level controls. These features do not all work like a traditional network firewall, so choosing the right combination matters.
In this article, we will look at how to control package dependencies in GitHub-based development, review proposed dependency changes, and build a safer workflow for .NET and other applications.
What Is a Package Firewall?
A package firewall controls which software packages can enter a development or build environment.
Traditional network firewalls control network traffic using rules. A package firewall applies a similar idea to software dependencies by allowing approved packages or sources and restricting packages that do not meet an organization's requirements.
For example, a development team might decide that:
- Production dependencies must come from approved package registries.
- New dependencies require a review before merging.
- Packages with known critical vulnerabilities cannot be introduced without an approved exception.
- Build systems must use locked dependency versions.
- Internal packages must come from the organization's trusted package feed.
These rules reduce the chance of introducing an unsafe dependency.
However, a package firewall is not a single universal feature in GitHub. You may need to combine GitHub security features with package registry permissions, repository configuration, and controls in your build environment.
Why Package Controls Matter
Consider a team maintaining an ASP.NET Core application. A developer adds a new NuGet package to solve a small problem. The package works locally, and the change passes a basic test.
But the package may introduce several additional dependencies. One of them might have a known vulnerability, or the package might come from an unexpected publisher.
Without dependency review, the team may not discover the risk until a later security scan or production incident.
Package controls help address three common problems.
Untrusted dependencies
An application may install packages from public registries or internal feeds. If developers can add packages without restrictions, it becomes harder to ensure that every dependency comes from an approved source.
Vulnerable packages
A dependency may contain a security flaw that affects your application. The risk depends on the vulnerability, the code paths used, and the way the package is configured.
Dependency confusion and package substitution
An attacker may attempt to make a package manager retrieve an unintended public package instead of an internal package with the same name. This is commonly associated with dependency confusion.
Clear registry configuration, package source controls, and appropriate access permissions can reduce this risk.
Step 1: Review GitHub's Available Dependency Controls
Before implementing restrictions, identify which GitHub features are available for your organization and repository.
| Feature | Main purpose | What it does not do by itself |
|---|
| Dependency review | Shows dependency changes introduced by a pull request | Does not automatically block every unapproved package |
| Dependabot alerts | Identifies known vulnerabilities in supported dependencies | Does not prevent all vulnerable packages from being installed |
| Dependabot security updates | Helps create pull requests to update vulnerable dependencies | Does not replace dependency approval policies |
| Dependency graph | Tracks dependencies that GitHub can identify | Does not guarantee that every dependency or source is detected |
| Repository rules and branch protection | Enforces selected repository and pull request requirements | Does not independently validate every package source |
| Package registry permissions | Controls access to supported package registries and packages | Does not replace application-level dependency scanning |
| CI/CD policy checks | Can reject dependency changes that violate defined rules | Requires a workflow and appropriate configuration |
The right combination depends on your GitHub plan, repository type, package manager, and organizational requirements.
Start with dependency review and vulnerability alerts. Add stricter source controls and CI checks where your threat model requires them.
Step 2: Enable Dependency Review for Pull Requests
Dependency review helps developers understand what changes when a pull request adds, removes, or updates dependencies.
For example, a pull request might update a NuGet package from version 4.2.0 to version 4.3.0. The review can help identify the dependency change and, where supported, associated vulnerability information.
This gives reviewers more context than simply looking at a modified project file.
A typical workflow is:
- A developer creates a branch.
- The developer adds or updates a dependency.
- A pull request is opened.
- Dependency review checks the dependency changes.
- Reviewers investigate unexpected packages or known security risks.
- The change is merged only after the required checks pass.
You can configure dependency review through a GitHub Actions workflow.
For example:
name: Dependency Review
on:
pull_request:
branches:
- main
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Review dependency changes
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
This example demonstrates the basic structure of a dependency review workflow. Confirm the supported action version and configuration for your repository before adopting it.
The fail-on-severity setting can reject dependency changes that introduce vulnerabilities at the selected severity or higher, subject to the action's behavior and available vulnerability information.
It is important to understand the limit: this workflow is a vulnerability review control, not a complete package allowlist. A package can be unapproved without having a known vulnerability.
Also, the workflow must be required through your repository's branch protection or ruleset configuration if merging should be blocked when the check fails. A workflow that runs but is not enforced may provide visibility without preventing a merge.
Step 3: Configure Package Sources for .NET Projects
For .NET applications, NuGet package sources determine where the package manager searches for dependencies.
A team might use nuget.org for public packages and a private feed for internal libraries. If package sources are not configured carefully, developers may retrieve packages from sources that the organization has not reviewed.
You can define package sources in a NuGet.Config file.
For example:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add
key="CompanyPackages"
value="https://packages.example.com/nuget/v3/index.json" />
<add
key="nuget.org"
value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
Replace the example company feed with your actual private package source.
The <clear /> element removes inherited package source entries at that configuration level before the listed sources are added. This makes the intended configuration easier to understand and maintain.
However, this configuration alone does not guarantee that a package will always come from the expected source. NuGet configuration can be affected by configuration hierarchy, command-line options, and other settings.
For internal packages, consider NuGet package source mapping.
A source mapping configuration can associate package ID patterns with specific feeds. This helps reduce the risk of retrieving an internal package from an unintended source.
For example:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add
key="CompanyPackages"
value="https://packages.example.com/nuget/v3/index.json" />
<add
key="nuget.org"
value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="CompanyPackages">
<package pattern="Company.*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
In this example, package IDs beginning with Company. are mapped to the internal feed. Other package IDs can be retrieved from nuget.org.
Adjust the patterns to match your organization's naming conventions. Test the configuration against existing dependencies before enforcing it across all developer machines and build agents.
Step 4: Add Dependency Checks to CI/CD
Developers should receive feedback before a dependency change reaches the main branch.
A CI pipeline can run dependency review, vulnerability checks, unit tests, and package restore validation. For stronger controls, it can also validate that package sources and lock files follow organizational policy.
For example, a .NET build workflow might include:
name: .NET Dependency Checks
on:
pull_request:
branches:
- main
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Restore packages
run: dotnet restore
- name: Check for vulnerable packages
run: dotnet list package --vulnerable --include-transitive
- name: Build application
run: dotnet build --no-restore
- name: Run tests
run: dotnet test --no-build
This example assumes that the project targets a compatible .NET SDK and that the selected SDK supports the commands shown. Update the SDK version to match your application.
The vulnerability command reports known vulnerable packages, but reporting a finding is not necessarily the same as failing the job. If your policy requires a failed build for certain findings, implement and test an explicit enforcement step or use a security tool that supports the required policy.
For production repositories, pin third-party GitHub Actions to verified full commit SHAs according to your organization's supply-chain policy. Version tags are easier to read, but a tag can potentially be moved.
Step 5: Control Dependency Versions
Source restrictions answer where packages can come from. Version controls answer which versions an application should use.
Unrestricted version ranges can make builds less predictable. A package update may introduce new behavior or a vulnerability without an intentional code change.
For .NET applications, commit the appropriate project files and consider enabling package lock files.
You can enable NuGet lock-file generation in a project file:
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
Restore the project to generate or update the lock file:
dotnet restore
Commit the generated packages.lock.json file where appropriate, and use locked restore in CI:
dotnet restore --locked-mode
Locked mode helps ensure that restore uses the dependency versions recorded in the lock file rather than silently changing the resolved dependency graph.
When updating a package, review the resulting lock-file changes along with the source-code changes. Do not treat a lock file as proof that a dependency is secure; it improves reproducibility but does not replace vulnerability scanning or package review.
Step 6: Build an Approved Package Policy
A package control system is more effective when developers know what is allowed.
An organization can define rules such as:
- Public packages must come from approved registries.
- Internal package IDs must resolve through designated private feeds.
- New dependencies require a clear technical reason.
- Packages with known critical vulnerabilities cannot be merged without an approved exception.
- Package updates must pass tests and dependency review.
- Exceptions must have an owner and a review date.
The policy should match the organization's risk level. A small application may not need the same approval process as a financial system or a service handling sensitive data.
Avoid blocking every package simply because it is new. Excessive restrictions can encourage workarounds, such as copying source files into repositories or using unapproved package feeds.
Instead, make the approved path easy to follow and provide a clear process for requesting exceptions.
Common Mistakes to Avoid
Assuming dependency review is a complete firewall
Dependency review helps identify changes and known risks. It does not automatically enforce every package-source or publisher policy. Add the controls needed to meet your requirements.
Relying only on vulnerability alerts
A package can be malicious or unapproved without having a published vulnerability record. Review package identity, source, ownership, and purpose as well as vulnerability status.
Configuring package sources without testing them
A restrictive NuGet configuration can prevent legitimate dependencies from restoring. Test it in a clean environment and in CI before rolling it out to every developer.
Allowing failed checks to be merged
A workflow does not necessarily block a pull request just because it reports a problem. Configure required status checks and branch rules, and test the enforcement behavior.
Forgetting transitive dependencies
Your project may directly reference only a few packages while those packages bring in many others. Review the complete dependency graph rather than checking only the dependencies listed in the main project file.
Best Practices for Managing Package Access
Start with visibility. Enable dependency tracking, review pull request changes, and investigate vulnerability alerts. Then define approved package sources and introduce enforcement gradually.
Keep package source configuration under version control where practical. Use lock files to make dependency resolution more predictable, and require CI checks before changes can merge into protected branches.
Assign responsibility for package exceptions. An exception should explain why the package is needed, who approved it, and when the decision should be reviewed.
Finally, test the controls themselves. Confirm that an unapproved source or a dependency that violates your policy is actually rejected. A security rule that only generates a report may not provide the protection your team expects.
Summary
A package firewall helps control which dependencies enter your development and build environments. In a GitHub-based workflow, you can build this protection by combining dependency review, vulnerability alerts, package source configuration, lock files, and CI/CD enforcement.
For .NET projects, NuGet package source mapping can help direct internal package IDs to the correct feeds. GitHub Actions can review dependency changes and run vulnerability checks before a pull request is merged.
The important distinction is that package visibility, vulnerability detection, source restrictions, and merge enforcement solve different problems. Choose controls for each requirement, test them in a separate environment, and make sure the rules are enforced rather than merely reported.
A well-designed dependency policy lets developers continue using the packages they need while giving the organization better control over software supply-chain risk.
Join the conversation! Your thoughts help the community grow.