Skip to main content

Refactoring Legacy .NET Apps often involves addressing outdated security practices, and if youโ€™ve ever worked on an old WinForms project, you know how common it is to find hardcoded credentials buried in the codebase. Back in the day, storing API keys, database passwords, and other secrets directly in code seemed like the easiest approach. But today? Itโ€™s a security risk waiting to happen.

In this guide, weโ€™ll walk through refactoring a legacy .NET app to remove hardcoded secrets and migrate them to ByteHide Secrets, a secure .NET-friendly secrets manager. If youโ€™re still managing secrets in your code, this is your chance to fix it the right way.

Index

Refactoring Legacy .NET Apps: Why Hardcoded Secrets Are a Security Risk

If you’ve worked on an old WinForms or ASP.NET project, you’ve probably seen API keys, database passwords, or encryption keys sitting right there in the code. Back then, this might have seemed like a harmless shortcut. No need to configure anythingโ€”just paste the credentials and go. Fast forward to today, and that “shortcut” is a security nightmare.

Iโ€™ve seen this happen way too many times. A project gets passed down, and suddenly, someone notices that production credentials have been sitting in Program.cs for years. The worst part? Those secrets might already be public without anyone realizing it.

Codebase Exposure: Your Secrets Might Already Be Public

One of the biggest dangers of hardcoded secrets is that they end up in source controlโ€”and once theyโ€™re there, youโ€™ve lost control.

Itโ€™s easier than you think:

  • A developer pushes a commit with credentials by accident.
  • Someone forks the repo, unknowingly sharing secrets.
  • Even in private repos, multiple team members can access credentials they shouldnโ€™t.

Remember the Uber breach in 2016? Hackers found AWS credentials hardcoded in a GitHub repo, giving them access to 57 million user records. Thatโ€™s all it tookโ€”one set of hardcoded credentials, publicly exposed.

If youโ€™ve never scanned your own repos for exposed secrets, try running Secrets Sprawl on one of your old projects. You might be surprised by what you find.

image 15

Hardcoded Secrets Never Change (And Thatโ€™s a Problem)

Another issue with hardcoded secrets is that they almost never get rotated. If you set an API key in 2018 and it’s still in use today, youโ€™re asking for trouble.

When secrets donโ€™t change, it means:

  • If they ever get leaked, attackers can use them indefinitely.
  • Theyโ€™re likely reused across multiple projects, making a single leak much worse.
  • Developers get comfortable assuming the credentials will โ€œjust work,โ€ forgetting to update them when needed.

A secrets manager like ByteHide Secrets makes rotation easier because you donโ€™t have to touch your code. If a key gets compromised, you just update it in the ByteHide Panel, and your application picks up the new value automatically.

Updating a Hardcoded Secret Shouldnโ€™t Require a Code Change

Every time a hardcoded secret needs to be updated, someone has to edit the code, recompile, and redeploy the application. Thatโ€™s not just inefficientโ€”itโ€™s risky.

Letโ€™s say youโ€™re working on a WinForms app, and the database password changes. If that password is hardcoded, you now have to:

  1. Find every place itโ€™s used.
  2. Change it manually.
  3. Recompile and distribute a new version.

Thatโ€™s assuming you even remember all the places the secret is being used. If different parts of your app rely on different copies of the same credentials, you could end up with half your application broken while the other half still works.

With a secrets manager, you donโ€™t need to touch the code when a secret changes. Instead, your app retrieves the latest credentials dynamically. Hereโ€™s an example using ByteHide ToolBox:

using Bytehide.ToolBox.Secrets;

Bytehide.ToolBox.Secrets.ManagerSecrets.Initialize("production");
var secrets = Bytehide.ToolBox.Products.Secrets;

string dbPassword = secrets.Get("DatabasePassword");

Now, whenever the password changes, the code remains the sameโ€”your app just fetches the updated value.

Hardcoded Secrets Violate Every Security Best Practice

If security compliance matters to you (or your company), hardcoded secrets are a major red flag.

  • OWASP lists secret exposure as a critical security issue.
  • NIST recommends keeping secrets in a dedicated vault, not in the source code.
  • GDPR & HIPAA require strict access control for sensitive data, and hardcoding credentials makes that nearly impossible.

Beyond compliance, hardcoded secrets increase attack surface. If an attacker gains access to your repo (even temporarily), they get everythingโ€”API keys, database logins, third-party service tokens. Thatโ€™s why migrating secrets to a dedicated secrets manager is more than a best practiceโ€”itโ€™s a necessity.

Time to Remove Hardcoded Secrets for Good

If your legacy .NET app still has hardcoded credentials, itโ€™s time to fix it before it becomes a problem.

In the next section, weโ€™ll walk through a step-by-step guide to identifying and migrating secrets out of your code and into a secure secrets manager like ByteHide Secrets. Youโ€™ll learn how to:
โœ… Scan your codebase for exposed credentials
โœ… Set up a secure secrets vault
โœ… Refactor your app to use dynamic secret retrieval

Letโ€™s get started.

Step 1 โ€“ Identifying Hardcoded Secrets in Your Legacy .NET App

Before we can secure anything, we need to find all the hardcoded secrets in our application. If you’re dealing with an old WinForms or ASP.NET project, there’s a good chance credentials are scattered across multiple filesโ€”maybe even duplicated in different parts of the codebase.

This step is all about tracking down every exposed secret before migrating them to a secrets manager like ByteHide Secrets.

Searching for Hardcoded Secrets Manually

The first thing I usually do is search through the code looking for common patterns. Some obvious ones include:

  • string password = "something";
  • var apiKey = "your-key-here";
  • connectionString = "Server=...;User Id=...;Password=...";

In Visual Studio, you can do a quick search using:

  • Ctrl + Shift + F (Find in Files)
  • Look for keywords like "password", "secret", "token", "key", "connectionString", etc.

But letโ€™s be honestโ€”this is slow and error-prone. You might catch some secrets, but what if someone used a non-standard variable name?

Automating the Search with Secret Scanning Tools

Instead of manually checking every file, we can use automated secret scanning tools.

Using ByteHide.Secrets.Integration for Continuous Detection

If you want to detect secrets automatically in every compilation and export them directly to a secure secrets manager, ByteHide offers a powerful solution.

First, youโ€™ll need a free ByteHide account. You can register here: cloud.bytehide.com/register.

Then, create a new ByteHide Secrets project. If you haven’t done this before, I already explained the setup process in Detecting and Managing Secrets in .NET.

Once your ByteHide account and project are set up, you can integrate it into your .NET application.

1. Install ByteHide Secrets Integration in your .NET project:

dotnet add package ByteHide.Secrets.Integration

2. Create a secrets.config.json file in the root of your project and configure it like this:

{
  "Name": "MyApp Secrets Configuration",
  "ProjectToken": "your-project-token-here",
  "Environment": "Production",
  "export": {
    "enabled": true,
    "encrypt": false,
    "prefix": "self_"
  }
}

This configuration does two important things:
โœ… Every time you compile, ByteHide scans your code for exposed credentials.
โœ… Instead of just listing secrets, it automatically exports them to the ByteHide Secrets manager, so you donโ€™t have to manually copy them.

For example, if we compile the application it will scan for secrets:

refactoring a legacy .NET apps

In our panel we can see the result of the scanner:

refactoring a legacy .NET apps

But as we also added the export information:

"export": {
  "enabled": true,
  "encrypt": false,
  "prefix": "self_"
}

The secrets will appear already imported into our manager without doing anything, like magic ๐Ÿช„

refactoring a legacy .NET app

This is a huge time-saver, especially for larger projects where manually tracking and copying secrets would take hours.

Alternative Tools for Secret Scanning

There are other tools designed to scan repositories for hardcoded secrets:

  • TruffleHog โ€“ Scans Git history for secrets using entropy analysis.
  • git-secrets โ€“ Prevents commits with hardcoded credentials.
  • Gitleaks โ€“ Detects secrets in repositories and prevents leaks before they happen.

If you want to explore a little more about these alternatives, I recently wrote an article showing how to integrate gitleaks into a .NET project, where I explained step by step how to do it!

These tools are useful for auditing your Git repository, but they have a major drawback:
๐Ÿ‘‰ They only detect secretsโ€”you still have to manually collect and import them into a secrets manager. (And yes, also, you must use a secrets manager tool)

With ByteHide.Secrets.Integration, you get automatic detection + migration + manager in one step, which makes the transition much smoother.

Creating an Inventory of Hardcoded Secrets

Before moving forward, itโ€™s a good idea to document the secrets youโ€™ve found.

If youโ€™re using ByteHide Secrets Integration, this happens automatically because detected secrets are exported. But if youโ€™re doing this manually, keeping a list helps:

[
  {
    "name": "DatabasePassword",
    "value": "hardcoded-password-here",
    "location": "Config.cs, line 42"
  },
  {
    "name": "ApiKey",
    "value": "abc123xyz",
    "location": "AuthService.cs, line 58"
  }
]

This inventory will be useful in Step 2, where we migrate secrets into ByteHide Secrets and remove them from the codebase.

Now that we know where the problems are, letโ€™s move on to securely storing these secrets the right way.

Step 2 โ€“ Setting Up ByteHide Secrets for Secure Secrets Management

Now that weโ€™ve identified all the hardcoded secrets in our legacy .NET app, itโ€™s time to move them to a secure secrets manager. This will ensure that credentials are stored safely, can be managed centrally, and are no longer hardcoded in our source code.

Weโ€™ll be using ByteHide Secrets to create a secure vault, organize secrets by environment, and replace the hardcoded ones we found earlier.

Creating a Secrets Project in the ByteHide Panel

To get started, you need a ByteHide account. If you havenโ€™t created one yet, you can sign up for free here: cloud.bytehide.com/register.

Once you’re logged in, follow these steps:

  1. Go to the ByteHide Panel.
  2. Click Create Project and choose Secrets as the protection type.
  3. Give your project a name (e.g., "LegacyApp-Secrets") and a brief description.
  4. Click Create Project, and your new secrets vault is ready.
image 19

This project will act as a centralized repository for storing and managing secrets across different environments.

Defining Multiple Environments

One of the biggest security issues in legacy apps is using the same credentials across all environments. This is a bad practice because:

  • Development credentials shouldnโ€™t have access to production resources.
  • A leaked staging API key shouldnโ€™t be able to interact with production databases.
  • Having different secrets per environment allows for better security segmentation.

If you want to know more about how you should actually manage your environments, I wrote an article here that talks about exactly this: .NET Multi-Environment Secrets: Dev, Staging, Prod Made Easy

To fix this, weโ€™ll create separate environments inside our ByteHide Secrets project:

  1. Navigate to your project and go to Settings.
  2. Under Environments, click Create New Environment.
  3. Add at least these three environments:
    • Development
    • Staging
    • Production
  4. Click Save.
image 20

Now, each environment will have its own isolated secrets, making it easy to apply different credentials depending on where the app is running.

Adding Secrets via the ByteHide Panel

Now that we have our vault and environments set up, letโ€™s start migrating secrets from our code to ByteHide Secrets.

1. In the ByteHide Panel, select the environment where you want to add secrets (e.g., Development).

2. Click Add Secret and enter the key-value pair.

  • Example:
    • Key: DatabasePassword
    • Value: dev-secret-password-123
image 21

3. Repeat this process for each environment (Development, Staging, Production) using the appropriate values.

image 22

4. You can also bulk import secrets by clicking Add Bulk Keys and pasting them in the format:

key1=value1
key2=value2
key3=value3

image 23

Once saved, these secrets will be securely stored and easily retrievable in our .NET application.

Whatโ€™s Next?

At this point, weโ€™ve successfully migrated secrets out of our codebase and into a secure secrets manager. But our app still isnโ€™t using themโ€”yet.

In Step 3, weโ€™ll modify our WinForms/.NET project to retrieve secrets dynamically from ByteHide Secrets, replacing the hardcoded values we removed.

Step 3 โ€“ Integrating ByteHide Secrets in the WinForms Application

Now that weโ€™ve migrated our secrets from the codebase to ByteHide Secrets, the next step is to modify our WinForms application so it retrieves secrets dynamically instead of relying on hardcoded values.

This ensures that credentials are securely stored, easily rotated, and never exposed in the source code.

Installing ByteHide ToolBox SDK

To interact with ByteHide Secrets in our WinForms application, we need to install the ByteHide ToolBox SDK.

Run the following command in your projectโ€™s root directory:

dotnet add package ByteHide.ToolBox

This package allows us to fetch secrets dynamically at runtime without hardcoding them in the source code.

Setting the API Key as an Environment Variable

For security reasons, ByteHide Secrets requires authentication using a Project Token. Instead of hardcoding it in the application, weโ€™ll store it in an environment variable.

On Windows:

set ByteHide.Secrets.Token=your-project-token-here

On macOS/Linux:

export ByteHide.Secrets.Token=your-project-token-here

By doing this, our application can securely authenticate with ByteHide Secrets without exposing the token in the source code.

Retrieving Secrets in Code Instead of Hardcoding

Now, letโ€™s modify our WinForms application to retrieve secrets dynamically.

1๏ธโƒฃ Import the ByteHide ToolBox SDK:

using Bytehide.ToolBox.Secrets;

2๏ธโƒฃ Initialize the secrets manager, specifying the environment:

Bytehide.ToolBox.Secrets.ManagerSecrets.Initialize("production");
var secrets = Bytehide.ToolBox.Products.Secrets;

3๏ธโƒฃ Retrieve secrets dynamically:

string dbPassword = secrets.Get("DatabasePassword");

Now, whenever our application needs to access the database password (or any other secret), it will fetch the correct value based on the environment instead of using a hardcoded credential.

Now we see what our checklist looks like:

โœ… Secrets are no longer hardcoded, making the application more secure.
โœ… Environment-specific secrets ensure that Development, Staging, and Production credentials are properly isolated.
โœ… Secrets can be updated dynamically in the ByteHide Panel without modifying the code.

Whatโ€™s Next?

At this point, our WinForms application is securely retrieving secrets from ByteHide Secrets instead of storing them in the code.

But security doesnโ€™t stop here. In Step 4, weโ€™ll fully refactor the code to clean up all the hardcoded credentials, ensuring that no exposed secrets remain in the project.

Step 4 โ€“ Refactoring the Code to Remove Hardcoded Secrets

Now that our WinForms application is configured to retrieve secrets dynamically from ByteHide Secrets, itโ€™s time to clean up the legacy hardcoded credentials and make sure everything runs smoothly.

This step ensures that our application is fully secured, with no hardcoded secrets left behind.

Replacing Hardcoded Secrets with Calls to ByteHide Secrets

In previous steps, we identified hardcoded credentials in the codebase, such as:

string dbPassword = "mySuperSecretPassword123";
string apiKey = "ABC-XYZ-123";

We now need to replace these with dynamic secret retrieval using ByteHide ToolBox.

โœ… Before (Hardcoded Secret in Code)

string dbPassword = "mySuperSecretPassword123";
var connectionString = $"Server=myServer;Database=myDB;User Id=admin;Password={dbPassword};";

๐Ÿš€ After (Retrieving Secrets Securely)

using Bytehide.ToolBox.Secrets;

Bytehide.ToolBox.Secrets.ManagerSecrets.Initialize("production");
var secrets = Bytehide.ToolBox.Products.Secrets;

string dbPassword = secrets.Get("DatabasePassword");
var connectionString = $"Server=myServer;Database=myDB;User Id=admin;Password={dbPassword};";

Now, whenever the database password needs to be changed, it can be updated directly in ByteHide Secrets without modifying the source code.

Updating Configuration Files for Secure Secret Management

If your application is using app.config or appsettings.json to store secrets, you should also refactor them to fetch values dynamically.

โœ… Before (Hardcoded in app.config)

<configuration>  
  <appSettings>  
    <add key="DatabasePassword" value="mySuperSecretPassword123" />  
  </appSettings>  
</configuration> 

๐Ÿš€ After (Fetching Secrets Dynamically in Code)

var dbPassword = secrets.Get("DatabasePassword");
ConfigurationManager.AppSettings["DatabasePassword"] = dbPassword;

Similarly, if you’re using appsettings.json:

โœ… Before (Hardcoded in appsettings.json)

{
  "DatabasePassword": "mySuperSecretPassword123"
}

๐Ÿš€ After (Retrieving Secrets Securely)

var dbPassword = secrets.Get("DatabasePassword");
Configuration["DatabasePassword"] = dbPassword;

By doing this, your configuration files remain clean and free from sensitive information.

Testing the Application After Refactoring

Once all hardcoded secrets are removed and replaced with ByteHide Secrets, itโ€™s essential to test the application to ensure:

โœ… Secrets are retrieved correctly for each environment (Development, Staging, Production).
โœ… The application works as expected with dynamically loaded credentials.
โœ… There are no remaining hardcoded secrets left in the source code.

A good practice is to temporarily log the secret keys (not values!) to confirm they are being retrieved from ByteHide Secrets:

foreach (var key in secrets.GetKeys())
{
  Console.WriteLine($"Loaded secret: {key}");
}

Step 5 โ€“ Deploying Securely and Ensuring Long-Term Security

At this point, our WinForms application no longer has hardcoded secrets, and it securely retrieves them from ByteHide Secrets. But security isnโ€™t just about fixing past mistakesโ€”itโ€™s about ensuring secrets remain protected in the long run.

A secure deployment strategy ensures that secrets donโ€™t leak, remain up to date, and are monitored for suspicious activity.

Keeping Secrets Out of Source Control

One of the most common ways secrets get leaked is accidentally committing them to Git. Even though weโ€™ve moved our secrets to ByteHide, itโ€™s still a good idea to prevent sensitive configuration files from ever being pushed.

โœ… Add These to Your .gitignore File

# Ignore secret config files  
secrets.config.json  
.env 

โœ… In CI/CD Pipelines, Use Secure Environment Variables

If your application is deployed via GitHub Actions, Azure DevOps, or GitLab CI, make sure secrets are injected as environment variables instead of being stored in the repository.

For example, in GitHub Actions, store your ByteHide Project Token as a GitHub Secret and reference it in your workflow:

env:  
  BYTEHIDE_SECRETS_TOKEN: ${{ secrets.BYTEHIDE_PROJECT_TOKEN }} 

This ensures that secrets are securely handled at runtime instead of being stored in the repository.


Automating Secret Rotation for Ongoing Security

Even with secrets securely stored in ByteHide, a leaked key is still a risk if itโ€™s never rotated. Regular secret rotation helps minimize the impact of compromised credentials.

To rotate secrets effectively:
1๏ธโƒฃ Use the ByteHide Panel to generate new credentials periodically.
2๏ธโƒฃ Update secrets in ByteHide, and all applications will pick up the new values automatically.
3๏ธโƒฃ Schedule rotations in CI/CD pipelines for services that support it (e.g., database passwords, API keys).

By keeping secrets fresh and short-lived, we significantly reduce their potential exposure.

Monitoring Secret Access with ByteHide Monitor

Secrets should not only be protected but also monitored for unusual activity. With ByteHide Monitor, you can:

  • Track who accesses your secrets and when.
  • Get alerts if secrets are accessed unexpectedly.
  • Detect unauthorized use before it becomes a security incident.

To enable monitoring, go to ByteHide Monitor in the panel and activate secret access tracking for your project.

For example, if an API key is suddenly being accessed from an unknown location, you can revoke it immediately and generate a new one.

giphy

Now Itโ€™s Your Turn โ€“ Secure Your .NET App Today!  

Refactoring a legacy .NET application to remove hardcoded secrets might seem like a small change, but it has a huge impact on security, maintainability, and compliance.

By moving credentials to ByteHide Secrets, weโ€™ve transformed an outdated approach into a modern, secure secret management workflow.

What have we achieved?

โœ… Stronger Security โ€“ No more hardcoded secrets that can leak into Git repositories.
โœ… Easier Maintenance โ€“ Updating secrets no longer requires code changes or redeployment.
โœ… Better Compliance โ€“ Aligns with OWASP, NIST, and industry best practices for secret management.

Whatโ€™s Your Experience?

Have you ever had to refactor secrets in a legacy .NET app? Did you find some “ancient secrets” buried in the code like a lost artifact? ๐Ÿ”

Or maybe you thought everything was secureโ€ฆ until you ran a scan and saw way too many secrets staring back at you? ๐Ÿ‘€

Tell me your best (or worst) secret management horror stories in the commentsโ€”Iโ€™d love to hear how you tackled it! ๐Ÿš€

Fill out my online form.

Leave a Reply