Skip to content

fix: persist DataProtection keys to filesystem (fixes #174)#179

Merged
hokiepokedad2 merged 10 commits into
mainfrom
feature/174-dataprotection-keys
Apr 12, 2026
Merged

fix: persist DataProtection keys to filesystem (fixes #174)#179
hokiepokedad2 merged 10 commits into
mainfrom
feature/174-dataprotection-keys

Conversation

@hokiepokedad2
Copy link
Copy Markdown
Contributor

@hokiepokedad2 hokiepokedad2 commented Apr 12, 2026

Summary

  • Configure ASP.NET Core DataProtection to persist keys to DATA_DIR/dataprotection-keys instead of ephemeral in-memory storage
  • Keys now survive container restarts, eliminating startup warnings about unpersisted keys
  • Uses the existing DATA_DIR env var (/app/data in Docker) with a ./data/ fallback for standalone dev
  • 18 exhaustive tests covering registration, persistence, round-trips, restart survival, config, and app name isolation

Details

  • No new NuGet packages — AddDataProtection() + PersistKeysToFileSystem() are included in the ASP.NET Core shared framework
  • No Dockerfile or docker-compose.yml changes — /app/data already exists and is volume-mounted
  • SetApplicationName ensures key ring isolation across environments
  • JWT tokens are unaffected — they use Jwt:Secret via HMAC-SHA256, independent of DataProtection
  • Path traversal guard added by GitHub security bot (commit 1a09108)

Test plan

  • dotnet build — 0 errors
  • dotnet test — 983/983 passing (18 new DataProtection tests)
  • dotnet format --verify-no-changes — no new issues
  • npx prettier --check — all files pass
  • npx jest --ci — 589/589 passing
  • Docker: verify ./data/dataprotection-keys/key-*.xml files appear after startup
  • Docker: verify warnings gone from docker compose logs | grep "No XML encryptor"
  • Docker: verify keys survive docker compose restart

Closes #174

@hokiepokedad2 hokiepokedad2 force-pushed the feature/174-dataprotection-keys branch from 5408b31 to f030172 Compare April 12, 2026 17:27
hokiepokedad2 and others added 3 commits April 12, 2026 14:14
Configure ASP.NET Core DataProtection to store keys in DATA_DIR/dataprotection-keys
instead of using ephemeral in-memory storage. Keys now survive container restarts.
Uses the existing DATA_DIR env var (Dockerfile/docker-compose) with a fallback to
./data/ for standalone dev.
…ne' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
18 tests covering: service registration, key file persistence, protect/unprotect
round-trips (various payloads, purpose isolation), key survival across provider
re-creation (simulated container restarts), DATA_DIR configuration, fallback path,
auto-directory creation, and application name isolation.
@hokiepokedad2 hokiepokedad2 force-pushed the feature/174-dataprotection-keys branch from f030172 to 9151968 Compare April 12, 2026 18:14
hokiepokedad2 and others added 6 commits April 12, 2026 14:16
- Add changelog entry under [Unreleased] > Fixed
- Add DataProtection config to CLAUDE.md Configuration section
- Add test file to File Locations table and Testing section
…ne' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ne' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ne' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ne' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ne' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
[Fact]
public void KeyFiles_AreWrittenToConfiguredDirectory()
{
var keysDir = Path.Combine(this._tempDir, "dataprotection-keys");
[Fact]
public void KeyFiles_AreValidXml()
{
var keysDir = Path.Combine(this._tempDir, "dataprotection-keys");
[Fact]
public void DataDir_UsesConfigurationValue_WhenSet()
{
var customDir = Path.Combine(this._tempDir, "custom-data");
{
var customDir = Path.Combine(this._tempDir, "custom-data");
Directory.CreateDirectory(customDir);
var keysDir = Path.Combine(customDir, "dataprotection-keys");
public void DataDir_CreatesKeySubdirectory_Automatically()
{
// DATA_DIR exists but dataprotection-keys subdirectory does not yet
var freshDir = Path.Combine(this._tempDir, "fresh");
// DATA_DIR exists but dataprotection-keys subdirectory does not yet
var freshDir = Path.Combine(this._tempDir, "fresh");
Directory.CreateDirectory(freshDir);
var keysDir = Path.Combine(freshDir, "dataprotection-keys");
[Fact]
public void ApplicationName_IsolatesKeyRings()
{
var dirA = Path.Combine(this._tempDir, "app-a");
public void ApplicationName_IsolatesKeyRings()
{
var dirA = Path.Combine(this._tempDir, "app-a");
var dirB = Path.Combine(this._tempDir, "app-b");
Directory.CreateDirectory(dirB);

// Use the same key directory but different application names
var sharedKeysDir = Path.Combine(this._tempDir, "shared-keys");
{
var services = new ServiceCollection();
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(dataDir, "dataprotection-keys")))
Remove unused EnsureRelativePath helper and restore consistent
Path.Combine usage in test file (hardcoded literals, no path
traversal risk). Keep Path.Join in production code where DATA_DIR
comes from user input.
@hokiepokedad2 hokiepokedad2 merged commit a090da5 into main Apr 12, 2026
4 checks passed
@hokiepokedad2 hokiepokedad2 deleted the feature/174-dataprotection-keys branch April 12, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ASP.NET Core DataProtection warnings — keys not persisted to storage

1 participant