Secure Storage Isn't Enough: Using Secrets Safely in Admin Automation
One of the best improvements in modern automation is the built-in secret manager. You can find it in many solutions from GitHub, Snowflake, and many many more.
Whether you’re using GitHub Actions, Jamf Pro, or another automation platform, you no longer need to hard-code passwords, API keys, or client secrets directly into scripts. That’s a huge improvement over the way many of us built automation years ago.
The mistake I still see isn’t storing secrets incorrectly. It’s assuming that storing them securely means the automation itself is secure. That is not necessarily true.
A secret manager protects a credential while it’s being stored. It doesn’t automatically protect how your workflow retrieves it, where it passes it, what gets logged, what temporary files are created, or how quickly you can recover if something goes wrong.
It really important to think about that as you architect your solution to ensure that remnants of these keys or tokens are not accidentally exposed.
GitHub Protects the Secret
GitHub Secrets solve the storage problem well. Repository, organization, and environment secrets provide a secure place for credentials that shouldn’t live in source control.
A typical workflow might retrieve a credential like this:
env:
API_TOKEN: $
At that point GitHub has done exactly what it promised. It stored the secret securely and injected it into the workflow. Once the secret leaves the secret store, however, the responsibility shifts from GitHub to the workflow itself.
Before I review the rest of the automation, I ask a few simple questions. Does the workflow really need this credential? Are the workflow permissions scoped as narrowly as possible? Is this running from a trusted trigger, or can user-controlled input influence the execution?
I also look for explicit permissions instead of broad defaults.
permissions:
contents: read
If a job needs additional rights to publish packages, create releases, or request OIDC credentials, I want those permissions granted only to that job.
The Workflow Now Owns the Secret
Once a secret reaches a script, the review changes. Now I’m looking for how the credential is used rather than where it came from. A simple API call is a good example.
curl -H "Authorization: Bearer $API_TOKEN" https://example.internal/api
Using GitHub Actions is ultimately where I want to end up, but my preference is to validate everything locally first. Running the automation on my own machine lets me observe exactly how it behaves before introducing GitHub Secrets or CI infrastructure.
For example, if the workflow normally runs a shell script, I can execute that same script directly from a terminal:
export API_KEY="your-api-key"
export API_TOKEN="your-api-token"
./scripts/run.sh
If the project uses a .env file, I can load it before running the script:
set -a
source .env
set +a
./scripts/run.sh
Or, if the workflow is just invoking a command, I can run that command exactly as the workflow would:
API_KEY="$API_KEY" python sync.py
Running the automation locally also lets me inspect its behavior in real time. I can verify whether it exits successfully, determine if any commands are unexpectedly echoed to the terminal, check whether temporary files are created, and review log output after both successful and failed runs.
The command itself isn’t inherently a problem. What matters is everything the automation does around it. Does the script enable shell tracing (set -x)? Does it print command output? Does it write temporary files? If an API request fails, does it dump request headers or response bodies into a log?
Answering those questions locally gives me confidence that the workflow isn’t inadvertently exposing sensitive information. Once I’m satisfied that the script behaves correctly and handles secrets safely, I can move the same process into GitHub Actions with minimal changes, knowing I’ve already validated the behavior outside of the CI environment.
Tokens Are Secrets Too
It’s easy to focus on protecting the original credential and overlook everything that happens afterward.
Many automation workflows use one secret to obtain another. A GitHub Secret, for example, might be exchanged for an OAuth access token from Jamf Pro or another API. While the client secret remains protected, the newly issued access token is every bit as sensitive.
That means the security review can’t stop once authentication succeeds. If an access token is written to a log file, captured in debug output, or saved in a temporary JSON file, the secret manager hasn’t failed—the automation has.
That’s why I evaluate every point where sensitive data is created, used, stored, or displayed, not just where the original credential is injected into the workflow. A well-protected secret can still be exposed if the automation mishandles the credentials it generates along the way.
Debugging Is Usually Where Things Change
Almost every automation project goes through a troubleshooting phase. The workflow fails, an API call returns an unexpected response, or authentication isn’t working as expected. To figure out what’s happening, it’s common to add temporary debugging commands.
For example, someone might enable shell tracing to see every command as it executes:
set -x
./deploy.sh
Or they may print a variable to verify that it was populated correctly:
echo "$API_TOKEN"
echo "$CLIENT_SECRET"
When an API call isn’t behaving as expected, it’s also common to enable verbose output:
curl -v \
-H "Authorization: Bearer $API_TOKEN" \
https://example.com/api/v1/devices
Other troubleshooting techniques include dumping the environment to verify variables are present:
printenv
Saving command output for later inspection:
./deploy.sh | tee debug.log
Or reviewing a temporary response file created during testing:
cat response.json
None of these commands are inherently unsafe. In fact, they can be invaluable while diagnosing a problem. The risk is that temporary debugging often becomes permanent. A command added during an incident can remain in a script long after the issue has been resolved.
When I review automation, I don’t automatically flag these commands as vulnerabilities. Instead, I ask why they’re still there. If they continue to provide operational value, they may be completely appropriate. If they’re leftovers from troubleshooting weeks or months ago, they’re worth removing before they expose information that was never intended to leave the workflow.
Rotation Should Never Be an Afterthought
Every credential will eventually need to be replaced. That might happen because someone changes roles, an API client is retired, a secret reaches its expiration date, or a token appears somewhere it shouldn’t.
A good way to evaluate an automation is to ask a simple question:
What would we do if this credential appeared in a log this afternoon?
The answer should be more than “generate a new one.” There should be a documented process that identifies where the credential lives, what uses it, and how it can be replaced without disrupting the workflow.
For example, if a GitHub Actions secret needs to be rotated, the process might look something like this:
# 1. Create a new API client or generate a new token
# (Performed in the application or identity provider)
# 2. Update the GitHub Secret
gh secret set JAMF_CLIENT_SECRET < new-secret.txt
# 3. Run the workflow to validate authentication
gh workflow run package-upload.yml
# 4. Confirm the workflow completed successfully
gh run list
Or, if you’re testing locally before updating GitHub, you might temporarily export the new credential and verify that everything still works:
export JAMF_CLIENT_SECRET="new-client-secret"
./scripts/package-upload.sh
Only after confirming the automation functions correctly would you replace the production secret and retire the old credential.
If no one knows where the secret is used, who owns it, or what depends on it, the automation may work perfectly today—but it isn’t operationally complete. Credential rotation shouldn’t require detective work during an incident.
Even a simple inventory like the following can make future rotations much easier:
Credential: Jamf Package Upload API Client
Stored in: GitHub Actions Secret
Used by: Package Upload Workflow
Permissions: Package upload only
Rotation: Annual review or suspected exposure
Owner: Mac Administration Team
The goal isn’t to make rotation complicated. It’s to make it predictable. When the process has been documented and tested ahead of time, replacing a credential becomes routine instead of a high-pressure recovery exercise.
Final Thoughts
Over the last few years, secret managers have become one of the biggest improvements in how we build and operate automation. Storing API keys, passwords, certificates, and tokens in a secure vault is unquestionably better than embedding them in scripts, configuration files, or source code. Modern platforms have made secure storage far easier than it used to be.
What I’ve learned, though, is that storing a secret securely is only one part of the problem. The moment an automation workflow retrieves that secret, it becomes responsible for everything that happens next. How is the credential used? Does it have only the permissions it actually needs? Could it accidentally appear in a log file or error message? Does the workflow create additional files that now contain sensitive information? If the credential is compromised, how difficult would it be to rotate it without disrupting the rest of the automation?
Those are the questions I spend the most time thinking about during a review. When I’m looking at a workflow, I rarely start by asking where the secret is stored. I assume a modern secret manager is doing its job correctly. Instead, I focus on how the workflow uses that secret after it’s been retrieved, because that’s where most of the security decisions are still being made.
A secret manager can protect a credential at rest, but it can’t tell you whether you’ve granted that credential too much access, logged it by accident, or built a workflow that’s difficult to recover if something goes wrong. Those are design decisions, and they’re still the responsibility of the person writing the automation.
For me, that’s the real takeaway. Good automation isn’t just about protecting secrets—it’s about designing workflows that continue to protect those secrets every time they’re used.
Sources
- GitHub Docs: Using secrets in GitHub Actions
- GitHub Docs: Security hardening for GitHub Actions
- GitHub Docs: Automatic token authentication
- Jamf Developer: Client Credentials
- OWASP Secrets Management Cheat Sheet
AI Usage Transparency Report
AI Era · Written during widespread use of AI tools
AI Signal Composition
Score: 0.23 · Moderate AI Influence
Summary
The article discusses the importance of secure secret management in automation workflows, highlighting the difference between storing secrets securely and protecting how they are used. It emphasizes the need for a thorough review of every stage where sensitive information is created, not just where it starts.
Related Posts
How I Keep Up With ISC2 CPE Credits Without Making It a Second Job
Keeping up with ISC2 CPE credits is easier when you treat it like a normal professional habit instead of a renewal emergency. Here is the system I use across CISSP, CCSP, SSCP, and CSSLP, with free and low-friction sources for webinars, books, training, and work-based credits.
When AI Agents Trust the Wrong Tool Description
Microsoft's MCP tool-poisoning research shows why AI agent security has to treat tool descriptions, schemas, and metadata as part of the control plane instead of harmless documentation.
Jamf Was My Mac Evidence Layer for CMMC
How Jamf Compliance helped support the Mac portion of a CMMC assessment, and why I added a small read-only CSV summary script for auditor-ready failed-result evidence.
Updating Jamf Pro Compliance Baselines from the macOS Security Compliance Project
How to update an existing Jamf Pro Compliance benchmark when new macOS Security Compliance Project baseline content becomes available.
The CMMC Evidence Collection Guide I Wish I Had Before My Assessment
When I started preparing for a CMMC assessment, I expected to spend most of my time focused on policies, procedures, and the System Security Plan. Those things are certainly important, but what surprised me was how much of the assessment ultimately came down to evidence.
How We Passed Our CMMC Assessment
After helping lead our organization through a successful CMMC Level 2 assessment, I share lessons learned from years of preparation, audit readiness, evidence collection, and working through the certification process.
Setting up Ollama on macOS
Recently, after some bad experiences with OpenAI's ChatGPT and CODEX, I decided to look into and learn more about running local AI models. On its face it was intimidating, but I had seen a lot of people in the MacAdmins community posting examples of macOS setups, which really helped lower the bar for me both in terms of approachability and just making me more aware of the local AI community that exists out there today.
AI Agent Constraints and Security
I really feel like in this era of AI it's essential to write about and share experiences for others who are leveraging AI, especially now that AI usage seems almost ubiquitous. Specifically, when it comes to AI in development and the rapid growth of AI-driven automations in the IT landscape, I believe there's a need for open discussion and exploration.
Vibe Coding with Codex: From Fun to Frustration
So there I was, a typically day, a typical weekend. As a ChatGPT customer, I had heard good things about Codex and had not yet tried the platform. To date my experience with agentic coding was simply snippit based support with ChatGPT and Gemeni where I would ask questions, get explanations and support with squashing bugs in a few apps that I work on, for fun, on the side. There were a few core features in one of the apps I built that I wanted to try implementing but the...
Turn Jamf Compliance Output into Real Audit Evidence
Most teams use Apple’s macOS Security Compliance Project (mSCP) baselines because they scale and they’re repeatable. Jamf’s tooling makes deployment straightforward and the Extension Attribute (EA) output is a convenient place to capture drift. What you don’t automatically get is the artifact an auditor will accept on a specific date—an actual document you can file that shows which endpoints are failing which items, plus a concise roll-up of failure counts you can act on. Smart Groups answer scope; they don’t produce evidence.