Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Code owners for libphonenumber-csharp.
# These owners are requested for review automatically when someone opens a PR
# touching matching paths. See:
# https://docs.github.com/articles/about-code-owners

# Default owner for everything in the repo.
* @twcclegg

# Security-sensitive automation and supply-chain config.
/.github/ @twcclegg
/.github/workflows/ @twcclegg
/appveyor.yml @twcclegg
2 changes: 2 additions & 0 deletions .github/workflows/build_and_run_demo_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build_and_run_unit_tests_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
Expand All @@ -52,12 +54,11 @@ jobs:
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.

# Run the broader hardened query suites in addition to the default set.
# security-extended adds higher-coverage (lower-precision) security queries;
# security-and-quality layers maintainability/reliability checks on top.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
queries: security-extended,security-and-quality

# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/run_performance_tests_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# OpenSSF Scorecard — supply-chain security posture analysis.
# Results are uploaded to the code-scanning dashboard and (via publish_results)
# to the public OpenSSF API that backs the Scorecard badge.
# https://github.com/ossf/scorecard-action
name: Scorecard supply-chain security

on:
# Re-run when branch protection settings change so the score stays current.
branch_protection_rule:
schedule:
- cron: '30 1 * * 6'
push:
branches: [ "main" ]
workflow_dispatch:

# Least privilege by default; the job below elevates only what it needs.
permissions: read-all

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to the code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false

- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# Publish results to the OpenSSF API so the README badge stays current.
# Only runs on the default-branch push / schedule events above.
publish_results: true

# Upload the results as artifacts (retained short-term) so they can be
# inspected even before code-scanning ingests them.
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5

# Upload to the code-scanning dashboard.
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: results.sarif
43 changes: 43 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Code of Conduct

## Our Pledge

This project is committed to providing a welcoming, harassment-free, and
respectful experience for everyone who participates — contributors, maintainers,
and users alike — regardless of background or experience level.

## Our Standard

We adopt the [Contributor Covenant](https://www.contributor-covenant.org), version
2.1, as the code of conduct for this project. The full text is available at:

https://www.contributor-covenant.org/version/2/1/code_of_conduct/

In short: be respectful and constructive, assume good intent, give and accept
feedback gracefully, and focus on what is best for the community and the project.
Behavior that is disrespectful, harassing, or otherwise unwelcoming is not
acceptable in any project space (issues, pull requests, discussions, and reviews).

## Scope

This Code of Conduct applies within all project spaces and also applies when an
individual is officially representing the project in public spaces.

## Reporting

If you experience or witness unacceptable behavior, please report it privately to
the project maintainers. You can do this by contacting the repository owner
([@twcclegg](https://github.com/twcclegg)) through GitHub, or by opening a
[private security advisory](https://github.com/twcclegg/libphonenumber-csharp/security/advisories/new)
if you prefer a confidential channel. All reports will be reviewed and handled
with discretion.

## Enforcement

Maintainers are responsible for clarifying and enforcing this Code of Conduct and
may take any action they deem appropriate, up to and including removing comments,
commits, code, and contributions, or banning a participant from the project, in
response to behavior they consider inappropriate.

This Code of Conduct is adapted from the Contributor Covenant; see the link above
for the enforcement guidelines and full reference.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[![Build status](https://ci.appveyor.com/api/projects/status/76abbk0qveot0mbo/branch/main?svg=true)](https://ci.appveyor.com/project/twcclegg/libphonenumber-csharp/branch/main)
[![codecov](https://codecov.io/gh/twcclegg/libphonenumber-csharp/branch/main/graph/badge.svg)](https://codecov.io/gh/twcclegg/libphonenumber-csharp)
[![NuGet](https://img.shields.io/nuget/dt/libphonenumber-csharp.svg)](https://www.nuget.org/packages/libphonenumber-csharp/)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/twcclegg/libphonenumber-csharp/badge)](https://scorecard.dev/viewer/?uri=github.com/twcclegg/libphonenumber-csharp)

C# port of Google's [libphonenumber library](https://github.com/google/libphonenumber).

Expand Down
58 changes: 58 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Security Policy

## Supported Versions

`libphonenumber-csharp` tracks upstream Google libphonenumber metadata releases and
ships frequent updates. Security fixes are applied to the latest released version on
NuGet only. Please make sure you are on the most recent release before reporting an
issue.

| Version | Supported |
| ------------------ | ------------------ |
| Latest release | :white_check_mark: |
| Older releases | :x: |

## Reporting a Vulnerability

**Please do not report security vulnerabilities through public GitHub issues,
discussions, or pull requests.**

Instead, report them privately using GitHub's
[private vulnerability reporting](https://github.com/twcclegg/libphonenumber-csharp/security/advisories/new).
This creates a private advisory that only the maintainers can see.

When reporting, please include as much of the following as you can:

- A description of the issue and the affected component (e.g. parsing, matching,
short-number handling, metadata loading).
- The version of the package you are using and the target framework.
- A minimal proof-of-concept or input that reproduces the problem.
- The potential impact (e.g. denial of service, incorrect validation result,
information disclosure).

We will acknowledge your report as quickly as we can and keep you updated on the
progress toward a fix and release.

## Scope and Threat Model

This is a library for parsing, formatting, and validating phone numbers. In typical
usage the **phone-number strings passed to the public API are untrusted**
(end-user input), while the **metadata shipped inside the assembly is trusted**.

Security-relevant reports we are particularly interested in include:

- Denial of service from untrusted input (e.g. excessive CPU/memory, catastrophic
regular-expression backtracking, unbounded allocation).
- Unhandled exceptions escaping the public API for inputs that should instead be
rejected with `NumberParseException`.
- XML external entity (XXE) or other parsing issues in the `Stream`-based metadata
loading constructor, when a consumer loads custom metadata.

Reports that require an attacker to supply malicious **metadata XML** are lower
severity, since metadata is normally trusted; we still want to know about them.

## Disclosure

We follow coordinated disclosure. Once a fix is available and released, we will
publish a GitHub Security Advisory crediting the reporter (unless you prefer to
remain anonymous).
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;CA1014;CA1031;CA1062;CA1707</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!--
Reproducible-build / Source Link settings; see PhoneNumbers.csproj for the
rationale. Source Link is in-box in the .NET 8+ SDK, so no PackageReference.
-->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<AnalysisLevel>latest</AnalysisLevel>
Expand Down
19 changes: 18 additions & 1 deletion csharp/PhoneNumbers/BuildMetadataFromXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;

namespace PhoneNumbers
Expand Down Expand Up @@ -102,7 +103,23 @@ internal static List<PhoneMetadata> BuildPhoneMetadataFromStream(Stream metadata
bool liteBuild = false, bool specialBuild = false, bool isShortNumberMetadata = false,
bool isAlternateFormatsMetadata = false)
{
var document = XDocument.Load(metadataStream);
// Load with a hardened reader. The public PhoneNumberUtil.CreateInstance(Stream)
// constructor accepts caller-supplied XML, so we guard that path against XML external
// entity (XXE) attacks. The metadata files carry a benign internal DTD subset (only
// <!ELEMENT> declarations, no entities), so we must still allow DtdProcessing.Parse;
// the security comes from XmlResolver = null (no external DTD/entity is ever fetched)
// plus a cap on characters produced by entity expansion (mitigates entity-expansion
// denial-of-service such as "billion laughs"). Modern .NET already defaults XmlResolver
// to null, but we set these explicitly as defense-in-depth (and to satisfy static
// analyzers such as CA3075).
var readerSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Parse,
XmlResolver = null,
MaxCharactersFromEntities = 1024 * 1024,
};
using var reader = XmlReader.Create(metadataStream, readerSettings);
var document = XDocument.Load(reader);

var metadataCollection = new List<PhoneMetadata>();
var metadataFilter = GetMetadataFilter(liteBuild, specialBuild);
Expand Down
10 changes: 10 additions & 0 deletions csharp/PhoneNumbers/PhoneNumbers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;CA1062;CA1707</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!--
Reproducible-build / Source Link settings. ContinuousIntegrationBuild normalizes
source-file paths embedded in the PDB so the published binary is deterministic; it
is gated to CI (the env var is set by GitHub Actions and AppVeyor) so local debug
builds keep their absolute paths. EmbedUntrackedSources embeds SDK-generated sources
(e.g. AssemblyInfo) that git doesn't track, so Source Link stays complete. Source
Link itself is in-box in the .NET 8+ SDK — no PackageReference is required.
-->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net9.0' Or '$(TargetFramework)' == 'net10.0'">
Expand Down
18 changes: 11 additions & 7 deletions lib/github-actions-metadata-update.sh
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
#! /bin/bash
# Exit bash script on any command that returns a non zero error code
set -e
# Exit on any error, treat unset variables as errors, and fail a pipeline if any
# stage fails. The pipefail matters here: every network read below is `curl | jq`
# (or `curl | grep | sed`), and without it a failed curl would feed empty input to
# the parser and the script would happily proceed with an empty version string —
# potentially cutting a bogus release. Fail closed instead.
set -euo pipefail

if [ $# -ne 1 ]
then
echo "GitHub token required"
exit 123
fi

if [ ! command -v jq &> /dev/null ]
if ! command -v jq &> /dev/null
then
echo "jq required"
exit 123
fi

getLatestGitHubRelease() {
curl "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name
curl --fail --silent --show-error --location "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name
}

getLatestNugetRelease() {
curl "https://www.nuget.org/packages/$1/" | grep 'og:title' | sed "s/.*$1 \([^\"]*\).*/\1/"
curl --fail --silent --show-error --location "https://www.nuget.org/packages/$1/" | grep 'og:title' | sed "s/.*$1 \([^\"]*\).*/\1/"
}

getReleaseDelta() {
curl https://api.github.com/repos/$1/compare/$2...$3 | jq .files[].filename
curl --fail --silent --show-error --location "https://api.github.com/repos/$1/compare/$2...$3" | jq .files[].filename
}

createRelease() {
curl -f -H "Authorization: Bearer $GITHUB_TOKEN" -d "{\"tag_name\":\"$2\",\",name\":\"$2\"}" "https://api.github.com/repos/$1/releases"
curl --fail --silent --show-error -H "Authorization: Bearer $GITHUB_TOKEN" -d "{\"tag_name\":\"$2\",\"name\":\"$2\"}" --location "https://api.github.com/repos/$1/releases"
}

GITHUB_TOKEN=$1
Expand Down