📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

Review this launchSettings.json. What does the file do, which tools use it, and what would you change?

A code review of an ASP.NET Core launchSettings.json: how launch profiles are used by Visual Studio, Rider, VS Code and dotnet run, and fixing committed secrets, the wrong environment and unsafe URLs.

Intermediate CodeReview Intermediate aspnet-core code-review launch-settings secrets user-secrets

🎯 Key points

  • launchSettings.json holds local launch profiles, not production configuration, and is not published
  • Used by Visual Studio, Rider, VS Code (C# Dev Kit) and dotnet run (first Project profile, or --launch-profile)
  • Committed secrets must be rotated immediately and removed from history; store local values in user secrets
  • Default profiles should use Development; test production-like behaviour in a separate, clearly named profile that uses non-production resources
  • The environment name changes behaviour (config files, error pages, user secrets); the production connection string is what reaches production data
  • Bind to localhost with HTTPS rather than 0.0.0.0 over HTTP
  • Production and CI configuration come from app settings, environment variables and Key Vault, not launch profiles

⏱ 30-second answer

Properties/launchSettings.json defines launch profiles for local development: how to start the app, which URLs, and which environment variables. Visual Studio, Rider, VS Code with C# Dev Kit and dotnet run all read it, but it is not published. In this file I would flag: secrets are committed (a production connection string and API key), so they must be rotated now and moved to user secrets; the environment is Production, which switches on production behaviour and turns off user secrets, and combined with the production connection string means a developer's F5 talks to the live database; and it binds to 0.0.0.0 over plain HTTP, exposing the app to the network. I would replace it with HTTPS and HTTP profiles on localhost using Development, and add a separate production-like profile only if someone needs to test production behaviour against non-production resources.

🗣 Full answer (senior)

How I would answer this in an interview

"Properties/launchSettings.json holds local launch profiles: how to start the app, which URLs and which environment variables. Visual Studio, Rider, VS Code with C# Dev Kit and dotnet run all read it, and dotnet run takes the first Project profile unless you pass --launch-profile, but it isn't published, so production and CI configuration come from app settings, environment variables and Key Vault. When I review it, I look for committed secrets, which I treat as compromised and rotate before cleaning history; the environment name, because Production switches off user secrets and developer pages, while the production connection string is what actually reaches production data; and 0.0.0.0 over plain HTTP, which I replace with localhost HTTPS and HTTP profiles on Development.

For example, on [our orders API], a production connection string ended up in a committed launch profile and a developer accidentally ran a migration against production. I led the clean-up and wanted to make it impossible to repeat. I had the credentials rotated straight away, worked with [the DBA] to roll back the migration, cleaned the history, moved local configuration to user secrets and a local SQL Server container, and replaced the profile with Development-only localhost profiles. I also pushed for managed identity, Key Vault and push protection. No data was lost [confirm in your own story], and since then [no] secrets have reached the repo.

The mistake I see is just deleting the line, which leaves the secret in history and in every clone. I review the file for what it enables, not just what it contains: if a profile can reach production, the real fix is least privilege, so developer machines can't reach production data at all, and that's part of keeping the whole team environment secret-free."


What the file is

Properties/launchSettings.json is a development-time file created by the ASP.NET Core templates. Each profile says how to launch the project:

  • commandName: Project (run the app itself with Kestrel), IISExpress, Executable or Docker.
  • applicationUrl: the URLs Kestrel listens on.
  • environmentVariables: set for that launch only, most importantly ASPNETCORE_ENVIRONMENT.
  • launchBrowser / launchUrl: whether to open a browser.

Who reads it

  • Visual Studio and Rider show the profiles in the run dropdown.
  • VS Code with C# Dev Kit can launch from them.
  • dotnet run uses the first profile with commandName: Project by default; --launch-profile <name> picks another and --no-launch-profile ignores the file.
  • dotnet publish does not include it, and the deployed app never reads it. Production configuration comes from appsettings.json, environment variables and Azure Key Vault.

Review findings for the file under review

# Finding Severity Fix
1 Production SQL password and live payment gateway key are committed Critical Rotate both now, remove from git history, report per the incident process; use dotnet user-secrets locally and Key Vault in Azure
2 ASPNETCORE_ENVIRONMENT is Production High Use Development so developer exception pages, user secrets and local config load, and production behaviour is not triggered locally
3 Connection string targets the production server High Developers should not be able to reach production data from a launch profile; point at a local SQL Server container
4 http://0.0.0.0:5000 binds every interface over HTTP Medium Use https://localhost:7181;http://localhost:5181
5 Single profile, no $schema Low Add https and http profiles and the schema for editor validation

Why a committed secret is critical even after deletion

Git keeps history, forks and clones keep copies, and scanners find secrets in public and internal repositories quickly. Treat the secret as compromised: rotate, audit access logs, then clean history. If personal information could have been accessed, assess it under the Notifiable Data Breaches scheme of the Privacy Act 1988.

Production example

A developer copied a production connection string into a launch profile to reproduce a bug, and later ran an EF Core migration from the IDE against production by accident. We rotated the credential, removed it from history, moved local configuration to user secrets and a local SQL container, and removed production network access from developer machines.

🔍 What the interviewer is checking

The interviewer is checking whether you know what this common file actually does, and whether you spot and respond correctly to a secrets leak.

Green flags

  • Knows which tools read the file and that it is not published.
  • Treats committed secrets as compromised: rotate, then clean history.
  • Fixes environment and binding issues and recommends user secrets and Key Vault.

Red flags

  • "Just delete the line."
  • Thinks the file configures production.
  • Misses the Production environment or the 0.0.0.0 binding.

📚 Learn in depth

1. What a launch profile actually is

A launch profile is a set of instructions that a tool reads before it starts your app. The app itself never opens launchSettings.json. The tool turns the profile into an ordinary process start: a command line, a working directory and a set of environment variables.

 Properties/launchSettings.json
         |
         v
 tool reads the selected profile
 (Visual Studio, Rider, VS Code C# Dev Kit, dotnet run, dotnet watch)
         |
         +-- applicationUrl        --> ASPNETCORE_URLS for the process
         +-- environmentVariables  --> process environment variables
         +-- commandLineArgs       --> args passed to Main
         +-- workingDirectory      --> current directory of the process
         +-- launchBrowser/launchUrl --> tool opens a browser (app unaware)
         |
         v
 your app starts and reads configuration as normal
 (it has no idea a launch profile existed)

That is why the file has no effect after dotnet publish: nothing in the deployed environment reads it.

2. Every common profile setting

Setting Meaning Notes
commandName How to launch Project (run the app with Kestrel), IISExpress, IIS, Executable (run another program), Docker (Visual Studio container tools)
applicationUrl URLs to listen on, separated by ; Becomes ASPNETCORE_URLS. Use localhost, not 0.0.0.0 or *
environmentVariables Variables set for this launch only ASPNETCORE_ENVIRONMENT, feature flags, logging levels. Never secrets
commandLineArgs Arguments passed to the app Read through args and the command-line configuration provider
workingDirectory Starting folder Affects relative paths such as appsettings.json lookup in console apps
launchBrowser / launchUrl Open a browser after start Set to false for APIs so the IDE doesn't open tabs
dotnetRunMessages Show "Building..." messages from dotnet run Cosmetic
inspectUri Browser debugging endpoint Used for Blazor WebAssembly debugging
iisSettings (top level) IIS Express site settings Only for IISExpress profiles; Windows only
$schema JSON schema reference Gives completion and validation in editors

3. Which tool reads it, and how

Tool How it picks a profile
Visual Studio The run dropdown lists every profile. The "Debug launch profiles" UI edits the same file
Rider Imports the profiles as run configurations automatically
VS Code with C# Dev Kit Launching a project offers its profiles. A launch.json entry of type dotnet can name one
dotnet run The first profile with commandName: Project, unless --launch-profile <name> (or -lp) is given. --no-launch-profile ignores the file
dotnet watch Same rules as dotnet run
dotnet test Does not use launch profiles
dotnet publish and the deployed app Ignore it completely

In .NET 10, file-based apps (dotnet run app.cs) can have launch profiles too, in a file named after the app such as app.run.json, next to the .cs file.

4. Where profile values sit in configuration

 launch profile  --sets-->  process environment variables
                                   |
 WebApplication.CreateBuilder(args) loads, later wins:
   1. appsettings.json
   2. appsettings.{Environment}.json
   3. user secrets              (only when Environment = Development)
   4. environment variables     <-- launch profile values land here
   5. command-line arguments    <-- commandLineArgs land here

Two consequences follow from this order:

  • A connection string in a profile overrides the safe local one in user secrets and in appsettings.Development.json, silently.
  • Setting ASPNETCORE_ENVIRONMENT=Production stops user secrets loading at all, and loads appsettings.Production.json instead. People then paste secrets into the profile to make it work, which is how the file in this question came to exist.

The double underscore in a variable name such as ConnectionStrings__OrdersDb maps to the : separator, so it becomes ConnectionStrings:OrdersDb.

5. What the environment name switches

Behaviour Development Production
appsettings.{Environment}.json loaded appsettings.Development.json appsettings.Production.json
User secrets Loaded Ignored
Developer exception page On in the default templates Off
app.Environment.IsDevelopment() branches True False
OpenAPI/Swagger UI in the templates Mapped Usually not mapped
Scope and service validation on build On Off

So the environment name changes real behaviour, but it isn't what reaches production data. The connection string is. Fixing only the environment name leaves the live database one F5 away.

6. User secrets internals

  • dotnet user-secrets init adds a <UserSecretsId> GUID to the project file.
  • Values are stored as plain JSON outside the repo: %APPDATA%\Microsoft\UserSecrets\<id>\secrets.json on Windows, ~/.microsoft/usersecrets/<id>/secrets.json on macOS and Linux.
  • They are not encrypted. They only keep values out of source control, so they are for development values only.
  • dotnet user-secrets list shows what is set; dotnet user-secrets clear removes everything for the project.

7. Responding to the committed secret

Order matters. Rotating first limits the damage; cleaning history first only hides the evidence.

  1. Rotate the SQL credential and the payment gateway key now. Assume both are compromised.
  2. Audit database and gateway logs for use of the old credentials since the commit date.
  3. Assess whether personal information could have been accessed. If so, follow the incident process, including the Notifiable Data Breaches scheme under the Privacy Act 1988.
  4. Remove the values from history with git filter-repo or BFG, force-push, and ask everyone to re-clone. Forks and existing clones still hold the old values, which is why step 1 comes first.
  5. Prevent a repeat: secret scanning with push protection, managed identity, and no network path from developer machines to production data.

8. A good team pattern

 repo (committed)                       developer machine (not committed)
 -----------------                      ---------------------------------
 launchSettings.json                    user secrets: local connection string,
   https / http profiles                              sandbox API keys
   ASPNETCORE_ENVIRONMENT=Development
   localhost URLs only                  local SQL Server container
 appsettings.json (no secrets)
 appsettings.Development.json
   (non-secret local defaults)

 Azure (deployed)
 ----------------
 App Service settings + Key Vault references, managed identity

If someone needs production-like behaviour locally, add a clearly named profile such as staging-like with ASPNETCORE_ENVIRONMENT=Staging that points at non-production resources.

9. Diagnosing "it uses the wrong settings"

  1. Check which profile ran. In the IDE look at the run dropdown; on the command line, dotnet run uses the first Project profile.
  2. Log app.Environment.EnvironmentName and the bound URLs at start-up.
  3. Log which configuration provider supplied a key without logging its value. In development, ((IConfigurationRoot)builder.Configuration).GetDebugView() shows each key and its source; never call it in production because it prints values.
  4. Look for machine-level environment variables with the same name, which override appsettings the same way the profile does.
  5. Run with --no-launch-profile to rule the file out.

10. Performance and security notes

  • Launch profiles have no runtime performance cost; they only affect how the process starts.
  • 0.0.0.0 or * exposes the app to every network the laptop joins. Combined with HTTP and the Development exception page, it can leak stack traces and data on shared Wi-Fi.
  • Keep launchSettings.json in code review and run a secret scanner on every pull request.

11. .NET 11 awareness

Launch profiles are stable and nothing in the .NET 11 previews changes the advice here. Check the release notes for dotnet run changes when .NET 11 ships.

12. Try it yourself

  1. Run dotnet new webapi -n Orders.Api and open Properties/launchSettings.json.
  2. Run dotnet run, then dotnet run --launch-profile https, and compare the URLs printed.
  3. Run dotnet user-secrets init, set ConnectionStrings:OrdersDb, and log whether it is present (not its value).
  4. Add the same key to the profile's environmentVariables and confirm the profile value wins.
  5. Change the profile to Production and confirm the user secret disappears.
  6. Run dotnet run --no-launch-profile and note that the app now uses default URLs and the Production environment.

💻 Code

The file under review (do not copy):

{
  "profiles": {
    "Orders.Api": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "http://0.0.0.0:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production",
        "ConnectionStrings__OrdersDb": "Server=orders-prod.database.windows.net;Database=Orders;User Id=orders_app;Password=Winter2026!;",
        "PaymentGateway__ApiKey": "live_4f9a2c..."
      }
    }
  }
}

The corrected Properties/launchSettings.json:

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "profiles": {
    "https": {
      "commandName": "Project",
      "launchBrowser": false,
      "applicationUrl": "https://localhost:7181;http://localhost:5181",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "http": {
      "commandName": "Project",
      "launchBrowser": false,
      "applicationUrl": "http://localhost:5181",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Local secrets instead (values stay in your user profile, outside the repo):

dotnet user-secrets init --project src/Orders.Api
dotnet user-secrets set "ConnectionStrings:OrdersDb" "Server=localhost,1433;Database=Orders;User Id=sa;Password=$LOCAL_SQL_PASSWORD;TrustServerCertificate=True" --project src/Orders.Api
dotnet run --project src/Orders.Api --launch-profile https

Reading it safely in Program.cs:

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("OrdersDb")
    ?? throw new InvalidOperationException("Connection string 'OrdersDb' is not configured.");

var app = builder.Build();
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
app.Run();

⚠ Common mistakes

  • Mistake: thinking launchSettings.json configures production. The file isn't published and the deployed app never reads it, so settings that only live there are missing in CI and production. Instead: put real configuration in appsettings.json, environment-specific files, App Service settings and Key Vault. Treat the launch profile as a local convenience only.

  • Mistake: putting secrets in a profile "because it's only local". The file is committed with the project, so every clone, fork, backup and CI log that prints the repo holds the secret. Instead: store local values with dotnet user-secrets and use sandbox credentials. Keep production secrets in Key Vault, reached through managed identity.

  • Mistake: deleting a leaked secret without rotating it. The value is still in git history, forks and every existing clone, and scanners may already have found it. Instead: rotate first, audit its use, assess privacy impact, then clean history with git filter-repo or BFG.

  • Mistake: not knowing configuration precedence. Profile values become environment variables, which override appsettings.json, appsettings.Development.json and user secrets. A connection string in the profile silently wins over the safe local one. Instead: keep connection strings out of profiles entirely. When behaviour is surprising, use GetDebugView() locally to see which provider supplied each key.

  • Mistake: using Production in the default profile. It turns off user secrets, loads appsettings.Production.json and disables the developer exception page, so developers start pasting production values in to make it work. Instead: default profiles use Development. If someone must test production-like behaviour, add a separate, clearly named profile that uses Staging and non-production resources.

  • Mistake: fixing the environment name and thinking the risk is gone. The environment name changes behaviour, but the production connection string is what reaches production data. Instead: remove the production connection string and remove the network path from developer machines to production databases.

  • Mistake: binding to 0.0.0.0 or * over HTTP. The app is reachable by anyone on the same network, often with the Development exception page on. Instead: use https://localhost:<port>;http://localhost:<port> and trust the dev certificate with dotnet dev-certs https --trust.

  • Mistake: believing user secrets are encrypted. They are plain JSON in the user profile. Anything on the machine running as that user can read them. Instead: use them for development values only, and never for production credentials.

  • Mistake: expecting dotnet test or CI to use the profile. dotnet test ignores launch profiles, so tests that pass in the IDE can fail in the pipeline. Instead: set the environment and configuration in the test itself, for example WebApplicationFactory<Program> with builder.UseEnvironment("Testing").

  • Mistake: assuming dotnet run uses the profile you see in the IDE. It uses the first Project profile in the file unless told otherwise, which may not be the one selected in Visual Studio. Instead: order the profiles deliberately and pass --launch-profile <name> in scripts and documentation.

  • Mistake: having no team rules for the file. It changes with every developer's experiment and drifts into unsafe settings. Instead: review it like code: Development or clearly named non-production profiles only, localhost URLs, no secrets, and secret scanning with push protection on every pull request.

🏛 Senior insight: trade-offs

Review the file for what it enables, not just what it contains. A launch profile that can reach production means production credentials and network access exist on developer machines, which is the real problem; removing the line only hides it. The senior fix is structural: least privilege (developers cannot reach production data), managed identity and Key Vault in Azure, secret scanning with push protection, and an incident process that rotates first and investigates second. That aligns with ACSC Essential Eight thinking on restricting administrative privileges and with Privacy Act obligations if customer data could be exposed.

Keep the boundary clear with CI/CD too. Pipelines and automated tests should never depend on launch profiles: dotnet test does not use them, and integration tests set their own environment explicitly, for example with WebApplicationFactory<Program> and builder.UseEnvironment("Testing") plus test-specific configuration. Deployed environments get configuration from the pipeline's variable groups, App Service settings or Key Vault references. If a value only exists in a launch profile, the app will behave differently in CI and production, which is itself a review finding.

🔗 Follow-ups the interviewer may ask

  1. When: The candidate says dotnet run uses the first Project launch profile. Why: Checks how the CLI uses launch profiles and what dotnet run actually does.
    How to answer

    The .NET CLI is the dotnet command that ships with the SDK. It runs apps and drives the SDK's build system, which is MSBuild underneath. dotnet new creates a project, solution or file from a template. dotnet build restores NuGet packages and compiles the project into bin/Debug/net10.0. dotnet run builds if needed and then runs the app, and anything after -- is passed to the app as arguments. dotnet clean deletes the outputs of a previous build. The same commands work on every OS and in CI, and IDEs call the same SDK underneath.

  2. When: The candidate moves secrets to user secrets and Key Vault. Why: Widens from one file to a reproducible, secret-free team environment.
    How to answer

    I standardise below the IDE rather than forcing one IDE. The CLI and CI are the source of truth. I pin the SDK with global.json, put style and analyser severities in .editorconfig, centralise build properties in Directory.Build.props and versions with Central Package Management, and commit shared IDE files such as .vsconfig, .vscode/extensions.json and launchSettings.json while ignoring personal state. I offer a Dev Container or a setup script for onboarding, keep secrets in user secrets and Key Vault, and CI runs the same dotnet format, build and test commands developers run locally.

🛠 Real-world problems: how you handle them

A launch profile pointed a developer's migration at production
Situation
To reproduce a data bug, a developer added the production connection string to the API's launch profile and set the environment to Production. The file was committed. Days later, another developer ran an EF Core migration from their IDE using that profile.
Signals
An unexpected schema change alert on the production database, a migration history row with a developer machine name in the logs, and a secret-scanning alert on the repository.
Investigation
Checked the database audit and migration history to confirm what ran and from where, reviewed the git history of `launchSettings.json`, and confirmed the credential had read-write rights and was reachable from the office network.
Resolution
Rotated the SQL credential and payment gateway key immediately, rolled back the migration in a controlled change, removed the secrets from git history, moved local configuration to user secrets and a local SQL Server container, and replaced the profile with Development-only localhost profiles.
Prevention
Enabled push protection for secrets, removed developer network access to production databases, moved the app to managed identity with Key Vault, and added a review checklist item for launch profiles.

How to tell it (STAR)

**Situation:** On [our orders API], a production connection string ended up in a committed launch profile and a developer accidentally ran a migration against production. **Task:** I led the clean-up and wanted to make it impossible to repeat. **Action:** I had the credentials rotated straight away, worked with [the DBA] to roll back the migration, cleaned the history, moved local config to user secrets and a local SQL container, and pushed for managed identity, Key Vault and push protection. **Result:** No data was lost [confirm in your own story], and since then no secrets have reached the repo.

↩ Usually asked after

More in IDEs and Editors

Last updated 1 Nov 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me