Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
eebb3a0
Fixes issue #2392 (#2419)
JanKallman Jul 1, 2026
70d829c
Versioning moved to Directory.Build.props and Build-release.yml adjus…
swmal Jul 2, 2026
66c2c62
Add link to security considerations for formulas
swmal Jul 7, 2026
2f9020e
Update link for security considerations in SECURITY.md
swmal Jul 8, 2026
86e1431
Bug/s1060 (#2426)
karlkallman Jul 8, 2026
4a66dfc
bug/s1063 SEARCH function Range on third arg (#2430)
OssianEPPlus Jul 9, 2026
5736cad
Merge pull request #2429 from EPPlusSoftware/bug/invalid-link-securit…
OssianEPPlus Jul 9, 2026
4d40481
#2431 Fix formula cfvo load error in ext icon sets (#2432)
swmal Jul 13, 2026
70384af
Add configurable WrapText measurement to AutoFitColumns (#2427) (#2428)
swmal Jul 21, 2026
1e4c96f
Added DisableImageFunctionDownloads property to ParsingConfiguration.…
swmal Jul 21, 2026
b125293
EPPlus version 8.6.2
JanKallman Jul 21, 2026
d8416ee
Updated version number on EPPlus.Interfaces and EPPlus.System.Drawing…
JanKallman Jul 21, 2026
21b0af6
Security updates 8.6.3 - System.Security.Cryptography.Xml (#2438)
swmal Jul 24, 2026
6eab939
#2443 - Fix header/footer picture loss when copying worksheets (#2444)
swmal Jul 31, 2026
c3e56f1
#2439 - Fix lookup regression when range starts before worksheet dime…
swmal Jul 31, 2026
5ff050e
#2441 - Added DisableImageFunctionDownloads property to ExcelCalculat…
swmal Jul 31, 2026
b2a49d9
Centralize version in Directory.Build.props; embed sub-project DLLs i…
swmal Aug 4, 2026
b04ec01
Merge develop9 into build-release; drop net35 and align net10 deps
swmal Aug 4, 2026
8ba5cc3
Merged develop8 -> develop9 and fixed conflicts
swmal Aug 4, 2026
5be5726
Merged from develop9
swmal Aug 4, 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
237 changes: 174 additions & 63 deletions .github/workflows/Build-Release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,44 @@ jobs:
with:
dotnet-version: '9.0.x'

# --- Read version and TFMs from csproj ---
- name: Read version and target frameworks from csproj
id: read_csproj
# --- Read version and target frameworks from Directory.Build.props ---
# Both Version and the shared target framework list are centralized in
# src/Directory.Build.props (shared by EPPlus and the four embedded sub-projects:
# DrawingRenderer, Export.Pdf, Fonts.OpenType, Graphics). EPPlus.csproj no longer
# holds a literal TFM list; it consumes $(EPPlusCoreTargetFrameworks), which a raw
# XML parser cannot expand. Therefore both values are read directly from props.
- name: Read version and target frameworks
id: read_version
run: |
$xml = [xml](Get-Content ./src/EPPlus/EPPlus.csproj)
$version = $xml.Project.PropertyGroup.Version | Where-Object { $_ } | Select-Object -First 1
$tfms = $xml.Project.PropertyGroup.TargetFrameworks | Where-Object { $_ } | Select-Object -First 1
$propsXml = [xml](Get-Content ./src/Directory.Build.props)
$version = $propsXml.Project.PropertyGroup.Version | Where-Object { $_ } | Select-Object -First 1
if ([string]::IsNullOrWhiteSpace($version)) {
Write-Error "Failed to read Version from src/Directory.Build.props. Aborting build."
exit 1
}

$tfms = $propsXml.Project.PropertyGroup.EPPlusCoreTargetFrameworks | Where-Object { $_ } | Select-Object -First 1
if ([string]::IsNullOrWhiteSpace($tfms)) {
Write-Error "Failed to read EPPlusCoreTargetFrameworks from src/Directory.Build.props. Aborting build."
exit 1
}

echo "VERSION=$version" >> $env:GITHUB_ENV
echo "TFMS=$tfms" >> $env:GITHUB_ENV
Write-Host "Version: $version"
Write-Host "Target frameworks: $tfms"
shell: pwsh

- name: Restore dependencies
run: dotnet restore ./src/EPPlus.sln

# --- SBOM ---
- name: Install CycloneDX
run: dotnet tool install --global CycloneDX
- name: Read version from csproj
id: read_version
run: |
$version = ([xml](Get-Content ./src/EPPlus/EPPlus.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } | Select-Object -First 1
echo "VERSION=$version" >> $env:GITHUB_ENV
shell: pwsh
- name: Generate SBOM
run: dotnet CycloneDX ./src/EPPlus/EPPlus.csproj -o ./sbom -F Json -st Library -sv ${{ env.VERSION }} -fn epplus-${{ env.VERSION }}.sbom.json -imp ./src/EPPlus/sbom-metadata-template.xml
- name: Generate SHA-256 checksum for SBOM
run: |
$sbomFile = "./sbom/epplus-${{ env.VERSION }}.sbom.json"
$hash = (Get-FileHash -Path $sbomFile -Algorithm SHA256).Hash.ToLower()
"$hash epplus-${{ env.VERSION }}.sbom.json" | Out-File -FilePath "./sbom/epplus-${{ env.VERSION }}.sbom.json.sha256" -Encoding utf8NoBOM
shell: pwsh
# --- SBOM ---

# Build the whole solution. Note: the EPPlus NuGet package is produced here, not by a
# separate 'dotnet pack' step. EPPlus.csproj has GeneratePackageOnBuild=true and a custom
# target (IncludeReferencedProjectsInPackage) that embeds the four sub-project DLLs into
# lib/<tfm>/ inside the package. That target only runs correctly as part of a build, because
# it depends on ReferenceCopyLocalPaths being populated; a standalone 'dotnet pack' produces
# an incomplete package containing only EPPlus.dll. The resulting .nupkg is picked up from
# src/EPPlus/bin/Release/ below.
- name: Build
run: dotnet build ./src/EPPlus.sln --no-restore --configuration Release
- name: Test
Expand All @@ -71,50 +76,162 @@ jobs:
creds: '{"clientId":"${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }}","clientSecret":"${{ secrets.EPPLUS_CODE_SIGNING_SECRET }}","subscriptionId":"${{ secrets.EPPLUS_CODE_SIGNING_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }}"}'

# --- Sign DLLs ---
- name: Sign EPPlus.dll with AzureSignTool
# All assemblies that ship inside a NuGet package are signed here, before the package is signed.
# EPPlus + the four embedded sub-projects (DrawingRenderer, Export.Pdf, Fonts.OpenType,
# Graphics) share the same target frameworks (EPPlusCoreTargetFrameworks, read into env.TFMS).
# EPPlus.Interfaces and EPPlus.System.Drawing are separate NuGet packages (unchanged from
# earlier versions) and are also signed here, as before.
# NOTE: this signs the DLLs in each project's bin/Release/<tfm> output. The embedded copies
# inside the EPPlus package are taken from these same signed outputs, so the package verify
# step below confirms the embedded DLLs carry a valid signature.
- name: Sign assemblies with AzureSignTool
run: |
$projectsToSign = @(
"EPPlus",
"EPPlus.Interfaces",
"EPPlus.System.Drawing",
"EPPlus.DrawingRenderer",
"EPPlus.Export.Pdf",
"EPPlus.Fonts.OpenType",
"EPPlus.Graphics"
)

$tfms = "${{ env.TFMS }}" -split ";"
foreach ($tfm in $tfms) {
$tfm = $tfm.Trim()
if ([string]::IsNullOrEmpty($tfm)) { continue }
$dll = ".\src\EPPlus\bin\Release\$tfm\EPPlus.dll"
Write-Host "Signing $dll"
azuresigntool.exe sign -kvu ${{ secrets.EPPLUS_CODE_SIGNING_KEY_VAULT_URL }} -kvi ${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }} -kvt ${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }} -kvs ${{ secrets.EPPLUS_CODE_SIGNING_SECRET }} -kvc ${{ secrets.EPPLUS_CODE_SIGNING_CERTIFICATE_NAME }} -tr http://timestamp.globalsign.com/tsa/advanced -td sha256 "$dll"

foreach ($project in $projectsToSign) {
foreach ($tfm in $tfms) {
$tfm = $tfm.Trim()
if ([string]::IsNullOrEmpty($tfm)) { continue }

$dll = ".\src\$project\bin\Release\$tfm\$project.dll"
if (-not (Test-Path $dll)) {
Write-Host "Skipping $dll (not built for this target framework)"
continue
}

Write-Host "Signing $dll"
azuresigntool.exe sign `
-kvu ${{ secrets.EPPLUS_CODE_SIGNING_KEY_VAULT_URL }} `
-kvi ${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }} `
-kvt ${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }} `
-kvs ${{ secrets.EPPLUS_CODE_SIGNING_SECRET }} `
-kvc ${{ secrets.EPPLUS_CODE_SIGNING_CERTIFICATE_NAME }} `
-tr http://timestamp.globalsign.com/tsa/advanced `
-td sha256 `
"$dll"
}
}
shell: pwsh
- name: Sign EPPlus.Interfaces.dll with AzureSignTool
# --- Sign DLLs ---

# --- Collect the built packages ---
# The packages are produced by the build (GeneratePackageOnBuild), not by 'dotnet pack'.
# We copy the three packages we actually ship (EPPlus, EPPlus.Interfaces, EPPlus.System.Drawing)
# into ./output for signing and upload. The four sub-projects and the benchmark project have
# IsPackable=false and therefore produce no package of their own; the sub-project DLLs ship
# embedded inside the EPPlus package.
- name: Collect NuGet packages
run: |
$tfms = "${{ env.TFMS }}" -split ";"
foreach ($tfm in $tfms) {
$tfm = $tfm.Trim()
if ([string]::IsNullOrEmpty($tfm)) { continue }
$dll = ".\src\EPPlus.Interfaces\bin\Release\$tfm\EPPlus.Interfaces.dll"
Write-Host "Signing $dll"
azuresigntool.exe sign -kvu ${{ secrets.EPPLUS_CODE_SIGNING_KEY_VAULT_URL }} -kvi ${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }} -kvt ${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }} -kvs ${{ secrets.EPPLUS_CODE_SIGNING_SECRET }} -kvc ${{ secrets.EPPLUS_CODE_SIGNING_CERTIFICATE_NAME }} -tr http://timestamp.globalsign.com/tsa/advanced -td sha256 "$dll"
New-Item -ItemType Directory -Force -Path ./output | Out-Null

$packageProjects = @(
"EPPlus",
"EPPlus.Interfaces",
"EPPlus.System.Drawing"
)

$found = $false
foreach ($project in $packageProjects) {
$pkgDir = ".\src\$project\bin\Release"
$pkgs = Get-ChildItem -Path $pkgDir -Filter "*.nupkg" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notmatch "\.symbols\.nupkg$" }

if ($null -eq $pkgs -or $pkgs.Count -eq 0) {
Write-Error "No .nupkg found for $project in $pkgDir. Expected GeneratePackageOnBuild to produce one."
exit 1
}

foreach ($pkg in $pkgs) {
Write-Host "Collecting $($pkg.Name) from $pkgDir"
Copy-Item $pkg.FullName -Destination ./output -Force
$found = $true
}
}
shell: pwsh
- name: Sign EPPlus.System.Drawing.dll with AzureSignTool
run: |
$tfms = "${{ env.TFMS }}" -split ";"
foreach ($tfm in $tfms) {
$tfm = $tfm.Trim()
if ([string]::IsNullOrEmpty($tfm)) { continue }
$dll = ".\src\EPPlus.System.Drawing\bin\Release\$tfm\EPPlus.System.Drawing.dll"
Write-Host "Signing $dll"
azuresigntool.exe sign -kvu ${{ secrets.EPPLUS_CODE_SIGNING_KEY_VAULT_URL }} -kvi ${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }} -kvt ${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }} -kvs ${{ secrets.EPPLUS_CODE_SIGNING_SECRET }} -kvc ${{ secrets.EPPLUS_CODE_SIGNING_CERTIFICATE_NAME }} -tr http://timestamp.globalsign.com/tsa/advanced -td sha256 "$dll"

if (-not $found) {
Write-Error "No packages were collected. Aborting."
exit 1
}
shell: pwsh
# --- Sign DLLs ---
# --- Collect the built packages ---

- name: Pack NuGet package
run: dotnet pack ./src/EPPlus.sln --configuration Release --output ./output
- name: Sign NuGet package
- name: Sign NuGet packages
run: |
NuGetKeyVaultSignTool.exe sign -kvu ${{ secrets.EPPLUS_CODE_SIGNING_KEY_VAULT_URL }} -kvc ${{ secrets.EPPLUS_CODE_SIGNING_CERTIFICATE_NAME }} -kvi ${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }} -kvs ${{ secrets.EPPLUS_CODE_SIGNING_SECRET }} -kvt ${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }} -tr http://timestamp.globalsign.com/tsa/advanced -fd sha256 -td sha256 -own EPPlusSoftware ".\output\*.nupkg"
- name: Upload NuGet package as artifact

# --- Verify the EPPlus package contains the embedded assemblies, correctly signed ---
# Automates the manual check that the four sub-projects are present in the final EPPlus
# package and signed with the same certificate as EPPlus.dll. This is the primary safety net:
# because packaging depends on build behaviour, this step must fail the build if the package
# is ever produced without all five embedded assemblies.
- name: Verify EPPlus package contents and signatures
run: |
$nupkg = Get-ChildItem ./output/*.nupkg |
Where-Object { $_.Name -notmatch "EPPlus\.Interfaces" -and $_.Name -notmatch "EPPlus\.System\.Drawing" } |
Where-Object { $_.Name -notmatch "\.symbols\.nupkg$" } |
Select-Object -First 1

if ($null -eq $nupkg) {
Write-Error "Could not locate the main EPPlus .nupkg in ./output."
exit 1
}
Write-Host "Verifying package: $($nupkg.Name)"

$extractPath = "./nupkg-verify"
if (Test-Path $extractPath) { Remove-Item $extractPath -Recurse -Force }
Expand-Archive -Path $nupkg.FullName -DestinationPath $extractPath -Force

# The five assemblies that must ship inside the EPPlus package.
$expectedDlls = @(
"EPPlus.dll",
"EPPlus.DrawingRenderer.dll",
"EPPlus.Export.Pdf.dll",
"EPPlus.Fonts.OpenType.dll",
"EPPlus.Graphics.dll"
)

$hasError = $false
foreach ($dllName in $expectedDlls) {
$found = Get-ChildItem -Path $extractPath -Recurse -Filter $dllName
if ($found.Count -eq 0) {
Write-Host "ERROR: $dllName not found anywhere in the package."
$hasError = $true
continue
}

foreach ($file in $found) {
$signature = Get-AuthenticodeSignature $file.FullName
if ($signature.Status -ne "Valid") {
Write-Host "ERROR: $($file.FullName) is not validly signed (status: $($signature.Status))."
$hasError = $true
} else {
Write-Host "OK: $($file.FullName) present and validly signed."
}
}
}

if ($hasError) {
Write-Error "Package verification failed. See errors above."
exit 1
}
Write-Host "All embedded assemblies are present and validly signed."
shell: pwsh
# --- Verify package contents ---

- name: Upload NuGet packages as artifact
uses: actions/upload-artifact@v4
with:
name: signed-nuget-package
name: signed-nuget-packages
path: ./output/*.nupkg

# --- SBOM (after build to avoid CycloneDX overwriting project.assets.json) ---
Expand Down Expand Up @@ -152,10 +269,4 @@ jobs:
--auth-mode login `
--overwrite
}
shell: pwsh
- name: Upload all SBOMs as artifact
uses: actions/upload-artifact@v4
with:
name: sbom
path: ./sbom/
# --- SBOM ---
shell: pwsh
1 change: 1 addition & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ We publish detailed security information including vulnerability disclosures, au
## See Also

- [EPPlus versioning](https://github.com/EPPlusSoftware/EPPlus/wiki/Releases-versioning)
- [Security-considerations-when-calculating-formulas](https://github.com/EPPlusSoftware/EPPlus/wiki/Security-considerations-when-calculating-formulas)
10 changes: 5 additions & 5 deletions appveyor8.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 8.6.1.{build}
version: 8.6.3.{build}
branches:
only:
- develop8
Expand All @@ -10,15 +10,15 @@ install:
& $env:temp\dotnet-install.ps1 -Architecture x64 -Version '10.0.100' -InstallDir "$env:ProgramFiles\dotnet"
init:
- ps: >-
Update-AppveyorBuild -Version "8.6.1.$env:appveyor_build_number-$(Get-Date -format yyyyMMdd)-$env:appveyor_repo_branch"
Update-AppveyorBuild -Version "8.6.3.$env:appveyor_build_number-$(Get-Date -format yyyyMMdd)-$env:appveyor_repo_branch"

Write-Host "8.6.1.$env:appveyor_build_number-$(Get-Date -format yyyyMMdd)-$env:appveyor_repo_branch"
Write-Host "8.6.3.$env:appveyor_build_number-$(Get-Date -format yyyyMMdd)-$env:appveyor_repo_branch"
dotnet_csproj:
patch: true
file: '**\*.csproj'
version: '{version}'
assembly_version: 8.6.1.{build}
file_version: 8.6.1.{build}
assembly_version: 8.6.3.{build}
file_version: 8.6.3.{build}
nuget:
project_feed: true
before_build:
Expand Down
18 changes: 15 additions & 3 deletions docs/articles/fixedissues.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# Features / Fixed issues - EPPlus 8
## Version 9.0.0
* Added 'Layout' property to 'ExcelChartTrendlineLabel' class.

## Version 8.6.3
### Security
* Updated System.Security.Cryptography.Xml to address five security vulnerabilities in the .NET XML signing dependency: four denial of service vulnerabilities (CVE-2026-47302, CVE-2026-50525, CVE-2026-50527, CVE-2026-50648) and one security feature bypass (CVE-2026-47304). The package is updated to 8.0.4 (.NET Framework, .NET 8 and .NET Standard), 9.0.18 (.NET 9) and 10.0.10 (.NET 10).
## Version 8.6 2
### Minor features and fixed issues
* Added property ´AlwaysRefreshImageFunction´ to ´ParsingConfiguration´, to disable download of external content in the calculation of the IMAGE function.
* Fixed unhandled ´InvalidOperationException´ when loading an icon set conditional formatting with formula cfvo from extLst.
* Fixed an issue where the SEARCH function did not handle arrays in the third argument.
* Fixed ´NullReferenceException´ in GetStyleId after multiple ´InsertColumn´ calls.
* AppVersion in ´OfficeProperties´ can now be set to null, to remove the value.
* Fixed drawing hyperlink reassignment crash and tooltip setting. (Fix by Lieven De Foor)
* Adds a WrappedTextAutofitMode property to ExcelTextSettings, backed by a new eWrappedTextAutofitMode enum, giving control over how cells with WrapText enabled contribute to column width in AutoFitColumns(). (Thanks to Lieven De Foor)
* Fixed ´KeyNotFoundException´ / ´ArgumentException´ on drawing rename and removal. (Fix by Lieven De Foor)
* Fixed unhandled ´IndexOutOfRangeException´ when importing text using the ´ExcelRangeBase.LoadFromText´ method, when text file has more columns than specified ´DataTypes´ argument. (Fix by Lieven De Foor)
* Fixed an issue when positioning shapes in charts.
## Version 8.6 1
### Features
* 3 new functions:
Expand Down
9 changes: 7 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<Project>

<PropertyGroup>
<!-- Shared version for EPPlus and its embedded sub-assemblies (DrawingRenderer, Export.Pdf, Fonts.OpenType, Graphics).
EPPlus.Interfaces and EPPlus.System.Drawing override this in their own Directory.Build.props. -->
<!-- Shared version for EPPlus and its embedded sub-projects -->
<AssemblyVersion>9.0.1.0</AssemblyVersion>
<FileVersion>9.0.1.0</FileVersion>
<Version>9.0.1</Version>

<!-- Shared target frameworks for EPPlus and the four embedded sub-projects.
Consumed explicitly per project via <TargetFrameworks>$(EPPlusCoreTargetFrameworks)</TargetFrameworks>.
NOT applied automatically, so separate packages (Interfaces, System.Drawing)
and Test/Benchmark projects are unaffected. -->
<EPPlusCoreTargetFrameworks>net8.0;net9.0;net10.0;netstandard2.1;netstandard2.0;net462</EPPlusCoreTargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
Expand Down
4 changes: 2 additions & 2 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.26" />
<PackageVersion Include="System.Formats.Asn1" Version="8.0.2" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="8.0.1" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.3" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="8.0.1" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.4" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.6" />
</ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/EPPlus.DrawingRenderer/EPPlus.DrawingRenderer.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0;netstandard2.1;netstandard2.0;net462</TargetFrameworks>
<TargetFrameworks>$(EPPlusCoreTargetFrameworks)</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>EPPlus Software AB</Company>
<Product>EPPlus</Product>
<Description>A spreadsheet library for .NET framework and .NET core</Description>
<IsPackable>false</IsPackable>
<NeutralLanguage />
<LangVersion>latest</LangVersion>
<SignAssembly>True</SignAssembly>
Expand Down
Loading
Loading