Proxy credentials end up in public build logs more often than you'd think. One careless curl -v, one variable in plain sight, one password inside a URL — and your login and password are sitting in the pipeline history, visible to the whole team. In this guide, we'll break down how to prevent that.

Introduction: How Proxy Credentials Leak into Logs and History

Picture a typical scenario. You're setting up a build that goes through the Proxeon proxy to reach external resources. To quickly test the connection, you write a command with the password right in the URL: http://user:pass@host:port. The build passes, and you're happy. But a week later, you find out the password is visible in the log of every run, in the shell history on the runner, and even in the client's debug output.

This isn't rare; it's the norm. Proxy credentials have an unpleasant trait: they're needed for almost every network call, so they easily seep into dozens of places at once.

What You'll Get by the End

After following this guide, you'll be able to configure your CI/CD so that proxy credentials never appear in any log, command history, or repository. You'll learn to use secrets in popular systems, disable dangerous debug modes, safely rotate passwords without stopping builds, and verify everything with a ready-made script.

Who This Guide Is For

This material is aimed at mid-level engineers: DevOps, backend developers, QA automation specialists. If you already know how to run a pipeline and what an environment variable is, you'll be comfortable. Advanced readers will find sections on rotation and auditing.

What You Should Know in Advance

We won't rehash the basic setup of http_proxy and no_proxy variables here — that deserves its own article. We'll assume your proxy connection already works, and your task now is to make credential storage secure.

How Much Time It Takes

Reading and understanding will take about 40 minutes. Implementing it in one pipeline takes 30 minutes to an hour and a half, depending on the CI system. You can add rotation and leak checking later; they take about 15–20 minutes each.

Preparation: What You'll Need

Before you start, gather everything you need. This will save time and prevent interruptions midway through.

Tools and Access

  • Access to your CI/CD project with rights to edit settings and secrets.
  • An active Proxeon proxy subscription with a login, password, host, and port.
  • A local machine with curl and git installed, plus Python 3 and Node.js interpreters if you plan to try those examples.
  • A text editor for editing pipeline configs.

System Requirements

There are no special requirements. Everything works on Linux, macOS, and Windows. CI runners are usually Linux-based, so most examples use bash syntax. We'll mention differences for Windows runners separately.

Prepare in Advance

Save your current proxy credentials in a reliable password manager. You'll need them when setting up secrets. Don't store them in a plain text file on your desktop.

Tip: Create a separate proxy account for CI if your Proxeon plan allows it. That way, a compromise of build credentials won't affect your personal credentials and vice versa.

Backup Configuration

Before changing your pipeline, make a copy of the current config file. Just copy .gitlab-ci.yml, your workflow file, or Jenkinsfile to a folder outside the repository.

⚠️ Warning: Never back up by committing to the same repository with a password inside. Even in a temporary branch, credentials end up in git history forever.

Basic Concepts: Terms Explained Simply

Let's clarify key terms so we don't get confused later.

Proxy Credentials

These are the login and password your client uses to prove it's allowed to use the Proxeon proxy. Sometimes IP-binding is used instead of a login/password pair, but here we're talking specifically about that pair.

CI Secret

A secret is a special storage area inside your CI system where you put sensitive values. The system encrypts them and injects them into builds as environment variables, automatically masking them in logs.

Masking

Masking is when CI replaces a secret's value in output with asterisks. If a password accidentally gets printed, you'll see something like [MASKED] instead. It doesn't always work perfectly, so we'll combine multiple layers of protection.

Proxy-Authorization Header

When a client authenticates to a proxy, it sends an HTTP Proxy-Authorization header with encoded credentials. In verbose debug mode, many clients print this header in full. Decoding it is trivial, so such output counts as a leak.

Scope

Scope determines which builds can access a secret. Properly scoped secrets are visible only to protected branches and never to forks or external pull requests.

Tip: Remember the core principle: a secret should exist in a process's memory only as long as needed and leave no traces on disk or in output.

Step 1: Understand the Biggest Trap — Password in URL

Goal: learn to recognize the most common source of leaks and abandon it for good.

One of the most common mistakes is putting credentials right in the proxy address: http://user:pass@host:port. It's convenient, so almost all beginners do it. The problem is that such a URL surfaces in unexpected places.

Where Exactly the Password in a URL Leaks

  1. Process list. The ps command on the runner shows the full command line, including the password. Any process on the same machine can read it.
  2. Proxy logs. Some server-side logs record the connection string. If the URL contains the password, it goes into the log.
  3. Shell command history. The .bash_history file stores everything you typed, including the password in the URL.
  4. Client debug output. Running with a verbose flag makes the client print the destination URL along with credentials.
  5. CI logs. If a variable containing the URL isn't marked as a secret, it gets printed in plain text during an echo or on error.

Check right now. Run a harmless command on any machine and look at the process list.

curl -x http://myuser:mypass@proxy.proxeon.net:8080 https://example.com & ps aux | grep curl

You'll see your password in plain text in the ps output. This is exactly the leak we're eliminating.

⚠️ Warning: Even if the build is private, another process, another job on a shared runner, or a monitoring tool might have access to the runner's process list. Treat a password in a URL as public.

Expected result: you understand the five leak points and will never write a password inside a proxy address again.

✅ Check: run the test command above and confirm you see the password in the ps output. If you see it, you've correctly reproduced the problem and are ready to fix it.

Step 2: The Right Way — Environment Variables and Credential Files

Goal: move credentials from URLs into secure storage — environment variables and dedicated files.

The idea is simple. Keep the login and password separate from the address. The client reads them from the environment or a file with restricted permissions, not from the command line. That way, they don't end up in the process list or shell history.

Option A: Environment Variables

Many clients can read proxy credentials from the environment. Let's go through examples.

curl Using an Environment Variable

Set the proxy address without credentials, and pass the login and password via a separate flag, taking the value from a variable.

  1. Export the variable with credentials into the environment (in CI, a secret does this; locally, a secure source).
  2. Pass its value via the -U flag, not in the URL.
export PROXY_CREDS="$PROXEON_USER:$PROXEON_PASS"; curl -x http://proxy.proxeon.net:8080 -U "$PROXY_CREDS" https://example.com

Even the -U flag with a variable is better than a password in a URL, but it's still visible in ps. That's why a credential file is preferable, as described below.

Python requests

In Python, read credentials from the environment via os.environ and assemble the proxies dictionary in memory. Don't print anything.

import os, requests; user=os.environ['PROXEON_USER']; pwd=os.environ['PROXEON_PASS']; proxy=f'http://{user}:{pwd}@proxy.proxeon.net:8080'; r=requests.get('https://example.com', proxies={'http':proxy,'https':proxy}); print(r.status_code)

Here, the password stays in a variable inside the Python process and never hits the command line. The key is not to log the proxy variable in full.

Node.js

In Node, also get credentials from process.env and build the proxy agent in code.

const user=process.env.PROXEON_USER; const pass=process.env.PROXEON_PASS; const proxyUrl=`http://${user}:${pass}@proxy.proxeon.net:8080`; const {HttpsProxyAgent}=require('https-proxy-agent'); const agent=new HttpsProxyAgent(proxyUrl); fetch('https://example.com',{agent}).then(r=>console.log(r.status));

Tip: In any language, log only the response status and, if needed, the destination host. Never print the entire proxy settings object — it contains the password.

Option B: .netrc File

The .netrc file is a classic way to store credentials apart from commands. curl reads it automatically.

  1. Create a .netrc file in the runner's home directory on the fly, from a CI secret.
  2. Write a line with the machine, login, and password.
  3. Set permissions to 600 so only the owner can read it.
  4. Run curl with the flag to use netrc.
printf 'machine proxy.proxeon.net login %s password %s' "$PROXEON_USER" "$PROXEON_PASS" > ~/.netrc; chmod 600 ~/.netrc; curl --netrc -x http://proxy.proxeon.net:8080 https://example.com

Now the password appears neither in the command line nor in the process list. It sits in a file with 600 permissions, which you'll delete at the end of the build.

⚠️ Warning: Permissions of 600 are mandatory. Without them, curl may refuse to read the file, and the file itself becomes accessible to other users on the machine.

Option C: curl Config File

curl can read flags from a config file. Put the proxy and credentials there, set permissions to 600.

printf 'proxy = http://proxy.proxeon.net:8080\nproxy-user = "%s:%s"\n' "$PROXEON_USER" "$PROXEON_PASS" > ~/.curlrc; chmod 600 ~/.curlrc; curl https://example.com

With a .curlrc file at 600 permissions, credentials aren't visible in the process or in command history.

Option D: Application Client Config

If you have your own application reading a config, store proxy credentials in a config file outside the repository. Keep only a template with placeholders in the repo, and substitute real values on the runner from secrets.

Expected result: in none of the examples does the password appear in the command line or process list. It lives either in a variable inside the process or in a file with 600 permissions.

✅ Check: run any example and simultaneously execute ps aux | grep curl. There should be no password in the output. If using netrc, check permissions with ls -l ~/.netrc — it should show -rw-------.

Step 3: Secrets in Popular CI Systems — GitHub Actions, GitLab CI, Jenkins

Goal: put proxy credentials into the protected storage of your CI system and inject them without leaks.

GitHub Actions

In GitHub, secrets are stored at the repository or organization level.

  1. Open your repository and go to Settings.
  2. On the left, find Secrets and variables, then Actions.
  3. Click New repository secret.
  4. Enter a name, like PROXEON_USER, and the value — your login. Save it.
  5. Repeat for PROXEON_PASS with the password.

In your workflow, reference secrets via the secrets context and pass them into steps as environment variables.

steps: - name: request env: PROXEON_USER: ${{ secrets.PROXEON_USER }} PROXEON_PASS: ${{ secrets.PROXEON_PASS }} run: printf 'machine proxy.proxeon.net login %s password %s' "$PROXEON_USER" "$PROXEON_PASS" > ~/.netrc; chmod 600 ~/.netrc; curl --netrc -x http://proxy.proxeon.net:8080 https://example.com

GitHub automatically masks secret values in logs. If a password accidentally gets printed, you'll see three asterisks.

⚠️ Warning: By default, secrets aren't available in workflows triggered from forks via pull_request. Don't switch that behavior to pull_request_target unless absolutely necessary — otherwise, an external contributor could obtain your credentials.

GitLab CI

In GitLab, secrets are called CI/CD variables and are configured in the project.

  1. Open your project, go to Settings, then CI/CD.
  2. Expand Variables and click Add variable.
  3. Enter the key PROXEON_USER and the value.
  4. Check the Masked box so the value is hidden in logs.
  5. Check the Protected box so the variable is only available to protected branches and tags.
  6. Repeat for PROXEON_PASS.

In .gitlab-ci.yml, variables are automatically available as environment variables.

request: script: - printf 'machine proxy.proxeon.net login %s password %s' "$PROXEON_USER" "$PROXEON_PASS" > ~/.netrc - chmod 600 ~/.netrc - curl --netrc -x http://proxy.proxeon.net:8080 https://example.com

Tip: The Masked flag in GitLab only works for values that meet certain rules: minimum length, no line breaks, base64-compatible character set. If a password isn't masked, GitLab will show a warning when saving. In that case, change the password to meet the requirements.

Jenkins

In Jenkins, credentials are stored in the Credentials section and injected via the Credentials Binding plugin.

  1. Go to Manage Jenkins, then Credentials.
  2. Choose a scope, like System and Global credentials.
  3. Click Add Credentials.
  4. Select the type Username with password.
  5. Enter the proxy login and password, and set a clear ID, like proxeon-creds.

In the Jenkinsfile, wrap usage in a withCredentials block. Jenkins masks values in the console.

withCredentials([usernamePassword(credentialsId: 'proxeon-creds', usernameVariable: 'PROXEON_USER', passwordVariable: 'PROXEON_PASS')]) { sh 'printf "machine proxy.proxeon.net login %s password %s" "$PROXEON_USER" "$PROXEON_PASS" > ~/.netrc; chmod 600 ~/.netrc; curl --netrc -x http://proxy.proxeon.net:8080 https://example.com' }

⚠️ Warning: Jenkins only masks values passed through Credentials Binding. If you assemble a password as a Groovy string and print it, masking won't work. Only work with credentials inside the withCredentials block and only in shell steps.

Expected result: credentials are in the protected storage of your CI system, injected into builds as environment variables, and masked in logs.

✅ Check: run a build and open the log. Confirm that the password is replaced by asterisks or a masking marker. Try intentionally echoing the variable — the system should hide it.

Step 4: Logs — Disable Modes That Print Proxy-Authorization

Goal: remove verbose output where it exposes the authorization header, and keep a safe logging level.

CI masking isn't a silver bullet. If a client prints the Proxy-Authorization header in base64, and the system doesn't know the original password verbatim, masking might not catch it. So we disable dangerous modes at the source.

curl

The -v flag and especially --trace print headers, including proxy authorization. In CI, use quiet mode.

  1. Remove -v, --verbose, --trace, and --trace-ascii from build commands.
  2. For error control, use -sS: quiet, but still shows errors.
  3. If you need debugging, use --trace only locally, never in CI.
curl -sS --netrc -x http://proxy.proxeon.net:8080 https://example.com -o /dev/null -w '%{http_code}'

This gives you just the response code without a single header in the output.

Python requests

The requests library doesn't print credentials by itself, but enabling urllib3 logging at DEBUG level outputs request headers. Keep logging at WARNING or INFO.

import logging; logging.getLogger('urllib3').setLevel(logging.WARNING)

Tip: If you really need DEBUG for diagnostics, add a logging filter that removes the Proxy-Authorization header from messages. But it's easier to diagnose locally, keeping CI at WARNING.

Node.js

In Node, avoid setting the NODE_DEBUG=http variable in CI — it outputs headers. Also, don't print the agent object or the full request object.

  1. Remove NODE_DEBUG from the runner's environment.
  2. In error handlers, print only error.message, not the whole object.
  3. Don't use HTTP request logging libraries in production builds.

⚠️ Warning: The stack trace of an unhandled exception can also contain a proxy URL with credentials if you built the URL with the password. This is another reason not to put the password in the URL, but to use netrc or separate fields.

Expected result: no tool in CI prints the authorization header or the full proxy URL.

✅ Check: run the build and search the log for Proxy-Authorization, Basic, and your username. There should be no matches.

Step 5: Rotating the Proxy Password Without Downtime

Goal: learn to change the password so builds don't fail, and the old password stops working.

You should change the proxy password periodically and immediately after any suspicion of a leak. The challenge is doing it without an outage window.

Overlap Strategy

The ideal approach is when both the old and new passwords work for some time. If your Proxeon plan allows creating a second account or an additional set of credentials, use that.

  1. Create new proxy credentials in your Proxeon dashboard without deleting the old ones.
  2. Add the new values to CI secrets under temporary names, like PROXEON_USER_NEW.
  3. Switch the pipeline to the new names in a separate branch and run a build.
  4. Confirm the build passes with the new credentials.
  5. Replace the main secrets PROXEON_USER and PROXEON_PASS with the new values.
  6. Remove the temporary secrets.
  7. Revoke the old credentials in your Proxeon dashboard.

This way, at any moment, there's a working set of credentials, and builds don't fail.

If Overlap Isn't Available

When only one set of credentials is available, act during a quiet window.

  1. Pick a time with minimal build activity.
  2. Pause new pipeline triggers for a couple of minutes.
  3. Change the password in the Proxeon dashboard.
  4. Immediately update the secret value in CI.
  5. Run a test build.
  6. Resume normal operations.

Tip: Write a short rotation procedure and keep it near your pipeline documentation. In a stressful situation after a leak, a ready list of steps saves nerves and time.

⚠️ Warning: After changing the password, be sure to delete any old netrc or curlrc files on the runner if they're cached between builds. Otherwise, the client will keep using the old credentials.

Expected result: the password is changed, new builds use the new credentials, and the old password no longer works.

✅ Check: try a request with the old password — it should return a proxy authorization error. A new build should pass successfully.

Step 6: Leak Check — Ready-Made Audit Script

Goal: ensure credentials aren't in artifacts, logs, or the repository, and automate this check.

Where to Look

  • Build artifacts: built files, reports, dumps.
  • Pipeline logs, including older runs.
  • Git repository history.
  • Runner caches and temp files.

Search in Artifacts and Logs

Download artifacts and logs to a local folder and search for telltale markers: username, part of the password, the word Basic, and the authorization header.

grep -RniE 'proxy-authorization|Basic [A-Za-z0-9+/=]{8,}|proxeon.*login|:[^@/]+@proxy' ./artifacts ./logs

The script searches for the authorization header, base64 strings after the word Basic, a login pattern in netrc, and a sign of a password in a URL before the @ symbol. Any match warrants investigation.

Search in Git History

A password might be in an old commit. Check the entire history.

git log -p -S 'proxy.proxeon.net' --all | grep -nE ':[^@/]+@proxy|password [^ ]+'

⚠️ Warning: If a password is found in git history, simply deleting the file isn't enough — it remains in old commits. You need to rewrite history with special tools and, more importantly, change the password immediately. Treat such a password as compromised.

Automating in the Pipeline

Add a separate job that scans built artifacts before publication and fails on a match. Such a safeguard catches leaks before they get out.

leak_check: script: - if grep -RniE 'proxy-authorization|Basic [A-Za-z0-9+/=]{8,}' ./artifacts; then echo 'LEAK DETECTED'; exit 1; fi

Tip: Additionally, set up secret scanners at the pre-commit stage locally. They catch credentials before they ever enter the repository.

Expected result: manual audit and automated job confirm that credentials are nowhere to be found.

✅ Check: run the audit script — it should finish with no matches. Then intentionally put a test string with a Basic marker into an artifact and confirm the script finds it and fails.

Step 7: Checklist Before Deploying the Pipeline

Goal: do a final review before enabling the pipeline.

Go through the checklist and mark each item. If even one isn't done, don't deploy.

  1. The proxy password is nowhere written inside a URL like user:pass@host.
  2. Login and password are stored only in CI secrets, not in repository files.
  3. Secrets are marked as masked and protected.
  4. Secrets aren't available to builds from forks or external pull requests.
  5. Verbose and trace flags are removed from commands.
  6. HTTP library logging level isn't DEBUG.
  7. Debug variables like NODE_DEBUG are absent from the runner environment.
  8. netrc and curlrc files are created on the fly and have 600 permissions.
  9. Credential files are deleted at the end of the build or live on an ephemeral runner.
  10. There's a leak-check job in the pipeline.
  11. Git history is checked and contains no credentials.
  12. There's a password rotation procedure.

Tip: Save this checklist as a template and attach it to every new pipeline that uses a proxy. Consistency reduces errors.

✅ Check: all twelve items are marked. Only then is the pipeline ready to deploy.

Verifying Results: How to Make Sure Everything Works

Let's combine the final check into a single scenario.

Functionality Checklist

  • The build successfully goes through the Proxeon proxy and gets the needed responses.
  • The build log contains no password, login, base64 authorization strings, or full proxy URL.
  • The runner's process list during a request shows no password.
  • Artifacts are clean; the audit script finds no matches.
  • Secrets are masked even when intentionally echoed.

How to Test

  1. Run the full pipeline from start to finish.
  2. Open the log and search for your username — there should be no matches.
  3. Download artifacts and run the audit script on them.
  4. Check the leak-check job — it should be green.

Success Metrics

Success looks like this: the build is green, requests through the proxy pass, and searching every possible place finds no trace of credentials. If that's the case, you've achieved the guide's goal.

Common Mistakes and Solutions

Let's go through frequent issues in a problem-cause-solution format.

Problem 1: Password Still Visible in the Log

Cause: the variable was created as a regular one, not a secret, or masking wasn't enabled.

Solution: move the value to the secrets section, enable masking, and verify the variable name matches both in the config and the storage.

Problem 2: Masking Doesn't Work in GitLab

Cause: the password contains characters or line breaks not allowed for masking.

Solution: change the password to a string of letters, digits, and allowed characters of sufficient length, meeting the masking requirements.

Problem 3: curl Doesn't Read netrc

Cause: the file has wrong permissions or isn't in the home directory.

Solution: set 600 permissions with chmod and make sure the file path matches expectations, or specify the path with the netrc-file flag.

Problem 4: Password in Stack Trace on Error

Cause: the proxy URL was built with credentials, and the client prints it in an exception.

Solution: switch to netrc or separate login/password fields so the URL contains no credentials, and print only the error message.

Problem 5: Old Password Still Used After Rotation

Cause: a cached netrc or curlrc file remains on the runner.

Solution: delete credential files at the end of every build and use ephemeral runners where the filesystem is wiped between runs.

Problem 6: Secret Leaked into Fork Pull Request

Cause: a mode is enabled that grants external PRs access to secrets.

Solution: disable such mode, run builds with secrets only for trusted branches, and do external PR validation without proxy access.

Problem 7: Password Found in Git History

Cause: credentials were once committed to a config file.

Solution: change the password immediately, then rewrite the repository history, removing sensitive data from all commits.

Additional Options: Advanced Protection

Once basic protection is in place, you can strengthen it further.

External Secret Manager

Instead of storing credentials in the CI system itself, connect an external secret manager. The pipeline gets credentials via a short-lived token only for the duration of the build. This way, secrets don't sit in project settings permanently.

Short-Lived Tokens Instead of Passwords

If Proxeon's infrastructure and your access scheme support it, prefer temporary tokens with a limited lifetime. Even if leaked, such a token quickly becomes useless.

Credential Separation by Environment

Use different credentials for test and production builds. Compromising test credentials won't affect production processes.

Tip: Set up alerts for unusual activity on the proxy account. A sudden spike in requests or connections from unexpected sources is a sign of a possible leak, requiring immediate rotation.

Automated Rotation

Advanced teams automate rotation on a schedule: a script creates new credentials, updates the secret, and revokes old ones without human intervention. Start with a manual procedure, then add automation once the process is refined.

FAQ: Frequently Asked Questions

Can I Just Trust CI Masking and Not Bother with Anything Else?

No. Masking only catches exact matches of known values. It can miss a base64 header or partial output. Combine masking with avoiding passwords in URLs and disabling verbose logs.

What's Safer: an Environment Variable or a netrc File?

A netrc file with 600 permissions is preferable for curl because the value doesn't appear even in the process list. For Python and Node code, environment variables read inside the process are more convenient. Both are safe with careful handling.

Do I Need to Delete the Credential File After the Build?

Yes, if the runner is reused. On ephemeral runners, where the machine is destroyed after the build, it's less critical, but deleting at the end is a good habit regardless.

What If the Proxy Password Contains Special Characters?

In netrc and separate fields, special characters are usually fine. Problems arise specifically when inserting into a URL, where characters like @ and : break parsing. This is another argument against putting the password in a URL.

Does requests Print Credentials on Its Own?

By default, no. Leaks happen when DEBUG logging for urllib3 is enabled or when you print the proxy settings object. Keep logging at WARNING and don't print settings in full.

Is the Password Visible in the Process List When Using the proxy-user Flag?

Yes, a flag on the command line is visible in ps. That's why for curl, prefer netrc or curlrc, where credentials aren't passed as arguments.

How Often Should I Change the Proxy Password?

Scheduled rotation is reasonable on a regular basis, for example quarterly, and always immediately if you suspect a leak. The rotation procedure from Step 5 will help you do it quickly.

Can I Store Credentials in an Encrypted File in the Repository?

Technically yes, but it complicates the process and creates a risk of leaking the encryption key. CI secrets and external secret managers solve the problem more simply and safely. Don't invent your own storage unless necessary.

What Should I Do If a Leak Has Already Happened?

Act in order: immediately change the proxy password, revoke old credentials, find all leak points with the audit script, rewrite git history if needed, and analyze the cause to prevent recurrence.

Do These Recommendations Work for Windows Runners?

Yes, the principles are the same. The syntax differs: instead of export, use the method for setting variables in your shell, and instead of chmod, adjust permissions via file properties. The logic of storage and disabling logs is identical.

Conclusion

You've gone from the unsafe habit of writing a password in a URL to fully protecting proxy credentials in CI/CD. Let's recap what's been done.

You learned to recognize five leak points and gave up passwords in proxy addresses. You moved credentials to environment variables and netrc, curlrc, and client config files with 600 permissions. You set up secrets in GitHub Actions, GitLab CI, and Jenkins with masking and scoping. You disabled verbose logs that print the authorization header in curl, Python requests, and Node. You mastered password rotation without build downtime and built a ready-made leak audit script. Finally, you went through the final pre-deployment checklist.

What to do next. Adopt the checklist as a mandatory review stage for all pipelines that use the Proxeon proxy. Add a leak-check job to all projects. Gradually transition to an external secret manager and short-lived tokens once the basic process becomes a habit.

Where to grow. Study secret management practices at the organizational scale, automated rotation, and anomaly monitoring on the proxy account. Credential security isn't a one-time setup; it's ongoing engineering discipline. But now you have a reliable foundation to build everything else on. Happy and secure builds.