TTechclick ⚡ XP 0% All lessons
BeyondTrust · Password Safe · Secrets & App CredentialsInteractive · L1 / L2 / L3

Secrets Safe & Application Credentials: — Killing Hardcoded Passwords with the API

Every password pasted into a script is a password nobody will ever rotate. This lesson shows the BeyondTrust way out: jobs that borrow credentials from Password Safe at runtime, team secrets parked in Secrets Safe — and API keys treated like the crown jewels they are.

📅 2026-06-10 · ⏱ 14 min · 3 live demos · 4 infographics · 🏷 10-Q assessment + AI Tutor inline

🎯 By the end you will be able to

Read as:

Pick where you want to start

1

The vicious cycle

Why hardcoded passwords never get rotated — and who profits.

2

Fetch at runtime

SignAppin → Requests → Credentials — real curl, PowerShell, Python.

3

Secrets Safe

Team tokens, certs and files in safes — owned, audited.

4

DevOps hygiene

CI/CD patterns plus protecting the API keys themselves.

🧠 Warm-up — 3 questions, no score

Just notice which ones make you pause. We answer all three inside the lesson.

1. The vault rotates svc_corebank’s password at 23:30 UTC tonight. The same password is hardcoded in four scripts. What happens at 02:00?

Answered in The vicious cycle.

2. Your Password Safe API key leaks in a public repo. What can a stranger on the internet do with it?

Answered in Secrets Safe.

3. Where should a team-shared Razorpay webhook signing secret live?

Answered in Fetch at runtime.

Most engineers think…

Most engineers think hardcoded passwords are a discipline problem — add a lint rule, scold the developer in code review, done.

Wrong — review catches the symptom, never the need. The script still needs the secret at runtime, so every "fix" just moves the copy: an env var, a CI variable, a config file outside the repo. The copy still exists, still goes stale at rotation, still leaks. The cure is architectural — AAPM: the job fetches the credential from Password Safe per run, holds it in memory for minutes, and hands it back. Zero copies at rest means rotation can finally run free.

① The hardcoded-credential problem — the password nobody dares to rotate

Walk through any Indian IT floor and you will find them: a database password inside deploy.sh, the same one in config.xml, again in a Jenkins pipeline variable, once more in a crontab, and — because someone was helpful in 2021 — pasted on the team wiki. That is secret sprawl: one credential, many homes, no owner. Each copy was created for a good reason (the job must authenticate unattended), and each copy is now a door an attacker can open with a single grep.

Here is the trap that makes it permanent. The audit team says rotate that password. But rotating it changes the target system only — the five copies keep the old value, so the 02:00 job fails, payments queue up, and the on-call engineer gets paged. After one such night, the team quietly files a rotation exemption. Now the password ages for years, ex-employees still know it, and the one control that would have contained a breach is switched off. Rotation breaks the script → so nobody rotates → so the secret never dies. That is the vicious cycle, and no code-review rule can break it.

👉 So far: copies make rotation scary, so rotation stops — and the password outlives the people who set it. Next: what the diagram of this mess looks like, and the runtime fetch that breaks the loop.
Figure 1 — One password, five homes — vs no home at all
Left red zone: one password copied into deploy.sh, config.xml, a Jenkins variable, a crontab and a wiki page, with an attacker reading every copy via grep, and a note that rotating it breaks five things so rotation stays off. Right blue zone: the script stores nothing and fetches from the Password Safe vault at runtime, so the vault rotates nightly and nothing breaks. One password, five homes — vs no home at all TODAY — secret sprawl deploy.sh PASS=Mumbai@2019 config.xml same password Jenkins env var same again crontab -l inline copy team wiki pasted in 2021 attacker grep -ri passw rotate it → five things break at 02:00 → so rotation stays OFF, for years five copies at rest = five doors for one thief AAPM — fetch at runtime run.sh no secret inside Password Safe vault + rotation fetch per run minutes, in memory at rest: ZERO copies of the password vault rotates nightly — nothing breaks every fetch = one audit row who · which account · when · from which IP the only thing stored: one scoped API key untrusted/attackertrusted/vaultedpolicy/decisionkey insightallowed/audited
Left: every red box is a standing copy an attacker can grep; the amber note is the vicious cycle. Right: the script stores nothing — it borrows from the vault per run, so rotation runs nightly and the only thing at rest is one scoped API key.

Think of it like the spare key under the flowerpot. The first copy felt convenient; then the maid got one, the neighbour got one, a cousin got one. Now changing the lock means chasing four people with new keys — so the lock is never changed, even after the cousin moved cities. The fix everyone actually wants is the AAPM model: stop distributing keys at all; let each visitor collect a fresh key from the security desk, on camera, and return it on the way out. In BeyondTrust language: the script authenticates to Password Safe with one scoped API key, borrows the real credential for minutes, and never stores it.

Four words that frame this lesson

Tap each card — these four ideas come back in every section.

🔁
The vicious cycle
tap to flip

Hardcode → copy → rotation breaks copies → rotation disabled → password ages for years. So: the fix is architecture, not discipline.

🌱
Secret sprawl
tap to flip

One credential quietly multiplies into scripts, configs, pipelines, wikis. Every copy is one more grep away from an attacker. So: count your copies.

🤝
AAPM
tap to flip

The app fetches from the vault at runtime, holds the secret in memory, gives it back. So: zero copies at rest.

💥
Blast radius
tap to flip

A leaked secret is bounded by what it reaches and how long it lives. Short release windows and a scoped runas shrink both. So: scope everything.

Sneha at Infosys faces this

Audit forced Automatic Password Management ON for svc_recon on PAY-APP-03 (172.16.8.31). The vault rotated it at 23:30 UTC. At 02:00 the reconciliation cron failed with "Login failed for user svc_recon" and three downstream jobs queued behind it.

Likely cause

The old password is hardcoded in /opt/recon/run.sh, one config file and a Jenkins variable — rotation changed the target system, not the copies.

Diagnosis

grep -r for the account name across the repo and Jenkins finds three standing copies; the Managed Accounts grid shows Last Change Date = last night, so the rotation fired exactly as designed.

BeyondInsight > Managed Accounts > svc_recon (Last Change Date / rotation history)
Fix

Convert the job to runtime retrieval: create an API registration plus a runas user holding the Requestor role on svc_recon, make run.sh call SignAppin → Requests → Credentials → checkin each run, then delete every stored copy.

Verify

Force one more rotation, re-run the cron by hand — it authenticates with the fresh secret. Rotation is now free to run nightly without a single page.

Quick check · Q1 of 10

Aditya at TCS finds svc_eod’s password unchanged since 2021, although Password Safe is licensed and Automatic Password Management works fine elsewhere. What is the most likely reason rotation is OFF for this account?

Correct: b. The vicious cycle: standing copies make rotation a guaranteed outage, so teams file exemptions and rotation is switched off. (a) is false — service accounts rotate fine when nothing hardcodes them; (c) is false — Unix/Linux, databases and network devices are all supported platforms; (d) password policies govern how new passwords are generated, they do not silently stop rotation.

Pause & Predict

Predict: ICICI’s auditor forces a rotation of svc_corebank tonight at 23:30 UTC. The password is hardcoded in four scripts. Describe Monday morning. Type your guess.

Answer: At 02:00 the first job fails to authenticate; everything downstream queues. On-call either restores the OLD password (defeating the audit) or hot-patches four scripts at 3 a.m. By Monday the team requests a rotation exemption — and the vicious cycle completes. The only durable fix is removing the copies: runtime retrieval via the API.

② The Password Safe API — borrow the credential, never store it

Everything starts with an API registration. In BeyondInsight you create one under Configuration → General → API Registrations: it issues a 128-character key and lets you attach Authentication Rules (allowed source IPs) and options like User Password Required. The key alone is not an identity — every call also names a runas user, and that user’s roles (Requestor, Requestor/Approver, ISA) decide what the call may touch. Two more facts worth an interview answer: there is an OAuth client-credentials alternative for application-type users under an API Access Policy registration, and client certificates are being phased out as an API-registration auth method in the 25.x line — plan new automation on keys or OAuth, not certs.

🖥️ This is the screen you’ll create the key on — BeyondInsight → Configuration → General → API Registrations → Create API Registration. (Recreated for clarity — your console matches this.)
bi.icicilab.example · Configuration → API Registrations
1
Registration Name
jenkins-cred-fetch
2
Registration Type
API Key Policy
3
Authentication Rule
IP Address · 10.20.30.41 (Jenkins runner only)
User Password Required
Off — key + runas only
Client Certificate Required
Off (deprecated as API auth in 25.x)
4
API Key
c479a66f83… (128 chars — copy once, vault it)
Create Registration

The header format is the thing students copy wrong most often, so read it once carefully: Authorization: PS-Auth key=<128-char key>; runas=<domain\user>; pwd=[<password>]; — the password sits in square brackets and appears only if the registration demands it. POST Auth/SignAppin opens a session (state is kept between calls), POST Auth/Signout closes it. Think Aadhaar OTP: nobody memorises a permanent code — each transaction gets a fresh, short-lived secret, so yesterday’s stolen OTP buys the thief nothing. Runtime retrieval gives your scripts the same property.

Figure 2 — Four calls — borrow, use, give back, rotate
Two swimlanes. Left lane is your job at 10.20.30.41, right lane is Password Safe behind the slash BeyondTrust slash api slash public slash v3 base path. Four numbered exchanges flow between them: SignAppin with key and runas, POST Requests which passes an amber access-policy and role check and returns request ID 4172, GET Credentials 4172 returning the secret into process memory only, and PUT checkin after which the vault rotates the password because Change Password After Release is set. Four calls — borrow, use, give back, rotate your job 10.20.30.41 · svc-jenkins-api Password Safe /BeyondTrust/api/public/v3 ① POST Auth/SignAppin · PS-Auth key + runas ② POST Requests {SystemId 57, AccountId 214} access policy + role check Requestor / ISA on the account? RequestId 4172 · approved for 10 min ③ GET Credentials/4172 → secret secret lives ONLY in process memory ④ PUT Requests/4172/checkin · POST Auth/Signout Change Password After Release → vault rotates untrusted/attackertrusted/vaultedpolicy/decisionkey insightallowed/audited
Follow the numbered arrows: SignAppin authenticates the job, Requests passes the amber access-policy + role gate, Credentials hands over the secret (memory only), and check-in lets Change Password After Release rotate it. The job never stores anything.
curl — sign in, then find the account (Password Safe REST API)
# 1) sign in — key from the API registration, runas = a real Password Safe user
curl -s -c /tmp/ps.cookies -X POST \
  -H "Authorization: PS-Auth key=c479a66f83…9484d; runas=icici\svc-jenkins-api;" \
  "https://bi.icicilab.example/BeyondTrust/api/public/v3/Auth/SignAppin"

# 2) find the account (works ONLY when Enable for API Access is ON)
curl -s -b /tmp/ps.cookies \
  -H "Authorization: PS-Auth key=c479a66f83…9484d; runas=icici\svc-jenkins-api;" \
  "https://bi.icicilab.example/BeyondTrust/api/public/v3/ManagedAccounts?systemName=PAY-DB-01&accountName=svc_corebank"
Expected output
{"UserId":318,"UserName":"icici\svc-jenkins-api","Name":"Jenkins API user"}
{"PlatformID":4,"SystemId":57,"SystemName":"PAY-DB-01","AccountId":214,
 "AccountName":"svc_corebank","DefaultReleaseDuration":120,
 "MaximumReleaseDuration":525600}   ← SystemId + AccountId feed the next call

▶ One credential fetch, start to finish

Watch a Jenkins job borrow svc_corebank for ten minutes and give it back. Press Play for the healthy path, then Break it to see the failure.

① SignAppinsvc-jenkins-api → POST Auth/SignAppin · PS-Auth key + runas
② RequestPOST Requests {SystemId 57, AccountId 214} → RequestId 4172
③ RetrieveGET Credentials/4172 → secret in process memory only
④ Check-inPUT Requests/4172/checkin → vault rotates per policy
Press Play to step through the healthy path. Then press Break it.
PowerShell — full borrow-use-return cycle (Requests workflow)
$base = 'https://bi.icicilab.example/BeyondTrust/api/public/v3'
$h    = @{ Authorization = 'PS-Auth key=c479a66f83…9484d; runas=icici\svc-jenkins-api;' }
Invoke-RestMethod "$base/Auth/SignAppin" -Method Post -Headers $h -SessionVariable ps | Out-Null

$acct = Invoke-RestMethod "$base/ManagedAccounts?systemName=PAY-DB-01&accountName=svc_corebank" -WebSession $ps -Headers $h
$req  = Invoke-RestMethod "$base/Requests" -Method Post -WebSession $ps -Headers $h -ContentType 'application/json' -Body (@{
          SystemId = $acct.SystemId; AccountId = $acct.AccountId
          DurationMinutes = 10; Reason = 'Nightly reconciliation run' } | ConvertTo-Json)
$secret = Invoke-RestMethod "$base/Credentials/$req" -WebSession $ps -Headers $h
# …use $secret in memory only, then hand it back:
Invoke-RestMethod "$base/Requests/$req/checkin" -Method Put -WebSession $ps -Headers $h -ContentType 'application/json' -Body '{}'
Invoke-RestMethod "$base/Auth/Signout" -Method Post -WebSession $ps -Headers $h
Expected output
4172                          ← POST Requests returns just the request ID
vY8#kQz2!mNp4@Lr9wTe6         ← GET Credentials/4172 — the live secret
                              ← checkin + Signout answer 204 No Content
(vault now rotates per policy — your stored-copy count stays ZERO)
Python — unattended job with the ISA role (no approval hop)
import requests

BASE = 'https://bi.icicilab.example/BeyondTrust/api/public/v3'
HDR  = {'Authorization': 'PS-Auth key=c479a66f83…9484d; runas=icici\\svc-isa-eod;'}

s = requests.Session()
s.post(f'{BASE}/Auth/SignAppin', headers=HDR).raise_for_status()

# ISA role = instant release: ONE call returns the credential itself
r = s.post(f'{BASE}/ISARequests', headers=HDR, json={
    'SystemId': 57, 'AccountId': 214, 'DurationMinutes': 10,
    'Reason': 'EOD reconciliation 02:00 IST'})
secret = r.json()          # str — held in memory, never written to disk
s.post(f'{BASE}/Auth/Signout', headers=HDR)
Expected output
201                       ← ISARequests: created, credential in the body
secret length: 24         ← print(len(secret)) — value itself never logged
204                       ← Signout
process exits → nothing persists on disk, in env vars, or in CI logs
MISTAKE — 200 OK but an empty list

Symptom: GET ManagedAccounts answers 200 with [] although the account is clearly onboarded. Three causes, in order of likelihood: Enable for API Access is off on the managed account; the runas user lacks a Requestor / Requestor-Approver / ISA role on it; a directory account was queried without UPN or DOMAIN\account format. Check the setting, the role, the format — in that order.

TIP — the 401 that is not an error

If the runas user has 2FA enabled, the FIRST SignAppin deliberately answers 401 with a WWW-Authenticate-2FA header — that is the challenge, not a failure. Resend SignAppin with challenge=<code>; appended to the PS-Auth header, keeping the same session. And treat the API key itself with the respect of the password it replaced: it IS a password.

👉 So far: SignAppin opens the session, Requests + Credentials hand you the secret, check-in lets the vault rotate freely — consumers always fetch fresh. Next: the secrets that have no managed system behind them at all.
Quick check · Q2 of 10

Meera at HCL wires up a new API user with 2FA enabled. Her first POST Auth/SignAppin returns 401 with a WWW-Authenticate-2FA header. What should her script do?

Correct: c. The 401 + WWW-Authenticate-2FA pair is the designed two-step challenge: the second SignAppin carries challenge=<code> in the same header. (a) wastes a SOC ticket on expected behaviour; (b) the identical call will 401 forever; (d) port 4422 is the session proxy for humans opening SSH sessions — unrelated to API auth.

Pause & Predict

Predict: Rahul at Wipro gets 200 OK with an empty array from GET ManagedAccounts, but the account exists in the console. Name the two most likely causes before you peek. Type your guess.

Answer: One: the managed account does not have Enable for API Access ticked — it is invisible to the API. Two: the runas user has no Requestor / Requestor-Approver / ISA role on that account. (Bonus third: a domain account queried without UPN or DOMAIN\account format.) None of these return an error — just an empty list, which is why this bites so many first integrations.

③ Secrets Safe — the team almirah for secrets with no machine behind them

Not every secret is a login on a server. A Razorpay webhook signing secret, a wildcard TLS certificate (.pfx), a Brevo API token, a licence file — none of these has a managed system Password Safe could rotate against. For these, BeyondInsight ships Secrets Safe (left menu → Secrets Safe). The model is teams: administrators assign BeyondInsight groups to teams, and each team gets an isolated store. Root folders are called safes, with folder trees underneath — the API returns a FolderPath with / as the separator.

Three secret shapes cover practically everything: a credential (username + password pair), a text secret (tokens, connection strings), and a file (certificates, keytabs, licence blobs). Every entry carries metadata — owner, created and modified dates, folder path — so six months later you know whose token that is. Think of the family Godrej almirah with per-member shelves: the bank locker (Password Safe) holds the property papers that need dual keys and a register; the almirah at home holds the everyday valuables each family member owns — still locked, still accounted for, but organised by shelf, not by bank procedure.

🖥️ Adding a team secret — BeyondInsight → Secrets Safe → (your team’s safe) → Add Secret. (Recreated for clarity — your console matches this.)
bi.icicilab.example · Secrets Safe
1
Secret Type
Text
2
Title
razorpay-webhook-secret
3
Folder
DevOps/Payments
Text
whsec_••••••••••••
4
Owner
Team: payments-devops (BeyondInsight group)
Create Secret
curl — Secrets Safe: read a team text secret by path + title
AUTH="Authorization: PS-Auth key=c479a66f83…9484d; runas=icici\svc-gitlab;"

# list by folder path + title (path separator is /)
curl -s -b /tmp/ps.cookies -H "$AUTH" \
  "https://bi.icicilab.example/BeyondTrust/api/public/v3/secrets-safe/secrets?path=DevOps/Payments&title=razorpay-webhook-secret"

# read the text value by the secret's GUID
curl -s -b /tmp/ps.cookies -H "$AUTH" \
  "https://bi.icicilab.example/BeyondTrust/api/public/v3/secrets-safe/secrets/8b1c2e54-77aa-4c1d-9f0e-3d2b6a91c4f7/text"
Expected output
[{"Id":"8b1c2e54-77aa-4c1d-9f0e-3d2b6a91c4f7","Title":"razorpay-webhook-secret",
  "Path":"DevOps/Payments","FolderPath":"DevOps/Payments"}]
"whsec_9hP2kL…K7tQ"       ← the value — pipe it straight into the app, store nothing
Figure 3 — Three stores — match the secret to the shelf
Three columns compare a Password Safe managed account, a Secrets Safe entry, and a developer-oriented vault such as HashiCorp Vault. Rows cover what each stores, whether it rotates, who consumes it, and when to pick it. A bottom rule of thumb says: machine credential with a system behind it goes to a managed account, shared team token or certificate goes to Secrets Safe, throwaway dynamic credentials for ephemeral infrastructure go to a dev secrets engine. Three stores — match the secret to the shelf Managed account stores: privileged creds WITH a managed system behind them rotation: YES — functional account + Password Change Agent consumers: humans (checkout/session) + apps via the API pick when: svc_corebank on PAY-DB-01, root on 192.168.40.17, sa on a DB Secrets Safe stores: team/app secrets with NO system: API tokens · certs · text · files rotation: NO engine — rotate at the source, update the entry consumers: teams = BeyondInsight groups, safes → folders, owned + audited pick when: Razorpay webhook secret, wildcard .pfx, Brevo API token Dev vault (e.g. HashiCorp) stores: dynamic secrets engines — creds minted per lease rotation: N/A — secrets are born short-lived, then expire consumers: code + ephemeral infra; YOU run and patch the tool pick when: throwaway DB creds for test containers, K8s-native flows Rule of thumb: system behind it → managed account · shared token/cert/file → Secrets Safe · minted-and-destroyed-in-minutes → dynamic dev engine. Audit story: PAM keeps one pane.
Read each column top to bottom: what it stores, whether it rotates, who consumes it, when to pick it. The deciding question is always the first row — is there a managed system behind this secret?

So when do you pick which? Managed account when a system + functional account exist behind the credential — you get the rotation engine, sessions and check-in for free. Secrets Safe when the secret is a thing your team shares but no rotation engine can touch — provider tokens, certs, files — and you want PAM-grade ownership and audit in the same console your vault lives in. A dev tool like HashiCorp Vault earns its place when engineering needs dynamic secrets — database credentials minted per lease for a test container and destroyed minutes later — or deep Kubernetes-native workflows; the trade-off is that you now operate, patch and audit a second secrets platform. Many Indian enterprises run both: Password Safe + Secrets Safe as the governed system of record, a dev vault inside the platform team’s cluster — with the dev vault’s own root credentials parked… in Password Safe.

VERIFY — is this secret on the right shelf?

Ask three questions. ① Is there a managed system + functional account behind it? Yes → managed account, so rotation works. ② Is it a shared team artefact a provider rotates (token, cert, file)? Yes → Secrets Safe, owned by a BeyondInsight group. ③ Is it created and destroyed in minutes by ephemeral infra? Yes → that is dynamic-secrets territory, a dev engine’s job. If you answered yes twice, the FIRST yes wins.

👉 So far: machine creds → managed accounts (rotation engine); team tokens, certs, files → Secrets Safe safes and folders; minted-per-lease creds → a dev engine. Next: wiring all of this into CI/CD without quietly creating a new key-shaped hole.
Quick check · Q3 of 10

Karthik’s team at Flipkart shares a wildcard TLS certificate (.pfx file) and a Brevo API token. Neither has a managed system behind it. Where do they belong?

Correct: a. No managed system = nothing for the rotation engine to act on, so a managed account (b) is the wrong shape — there is no platform to onboard. Secrets Safe exists exactly for this: file + text secrets, safe/folder structure, group ownership, full audit. (c) is sprawl with extra steps; (d) confuses the public certificate with the PRIVATE key inside a .pfx — that key is a crown jewel.

Pause & Predict

Predict: a developer asks for the team’s Razorpay token "just once, over WhatsApp". What does the Secrets Safe version of this favour look like? Type your guess.

Answer: The token lives as a text secret in DevOps/Payments inside the team’s safe. The developer’s BeyondInsight group already grants read — they fetch it from the console or the secrets-safe API, and the access lands in the audit trail with their name on it. The WhatsApp copy, by contrast, is an untracked, unrevokable clone on two phones and a backup. Same favour, zero sprawl.

④ DevOps patterns + API hygiene — protect the key that opens the vault

The CI/CD pattern is the same everywhere — GitLab CI, Jenkins, GitHub Actions, Azure DevOps: the runner’s secret store holds exactly one secret (the API key), and every job fetches per run with a short DurationMinutes, uses the value from an environment variable that dies with the process, checks the request back in, and signs out. No masked pipeline variables full of passwords, no echo $DB_PASS in the log, no artefact files. Short-lived retrieval beats caching every single time: a cache is a copy, and a copy is exactly the thing this whole lesson kills.

CI/CD — fetch-cred.sh runs inside the job; only the API key is injected
#!/usr/bin/env bash
# Only PS_API_KEY comes from the runner's secret store.
# The DB password itself is never stored anywhere.
set -euo pipefail
BASE='https://bi.icicilab.example/BeyondTrust/api/public/v3'
AUTH="Authorization: PS-Auth key=${PS_API_KEY}; runas=icici\svc-gitlab;"
CJ=$(mktemp)

curl -sf -c "$CJ" -H "$AUTH" -X POST "$BASE/Auth/SignAppin" >/dev/null
REQ=$(curl -sf -b "$CJ" -H "$AUTH" -H 'Content-Type: application/json' \
      -d '{"SystemId":57,"AccountId":214,"DurationMinutes":5,"Reason":"CI deploy"}' \
      -X POST "$BASE/Requests")
export DB_PASS=$(curl -sf -b "$CJ" -H "$AUTH" "$BASE/Credentials/$REQ" | tr -d '"')

./deploy.sh        # reads DB_PASS from the environment — in memory, this run only

curl -sf -b "$CJ" -H "$AUTH" -X PUT  "$BASE/Requests/$REQ/checkin" -H 'Content-Type: application/json' -d '{}'
curl -sf -b "$CJ" -H "$AUTH" -X POST "$BASE/Auth/Signout"
Expected output
deploy.sh: connected to PAY-DB-01 as svc_corebank
deploy.sh: schema migration 042 applied
checked in request 5217 — vault rotates per policy
job log shows ZERO secrets — only the request ID
MISTAKE — caching the fetch "to save API calls"

Symptom: someone optimises the pipeline by writing the fetched password into a CI variable; weeks later every job fails just after 23:30 UTC. Cause: the cached fetch is a brand-new hardcoded copy — it went stale at the next scheduled rotation, exactly like the script copies in section ①. Fix: fetch per run with DurationMinutes 5–10 and check in immediately after use. The API round-trip costs milliseconds; the outage costs a night.

Now the uncomfortable part: your automation is only as safe as the key it authenticates with. In December 2024, attackers stole a Remote Support SaaS API key from BeyondTrust itself, used it to reset local application passwords, and reached 17 SaaS customers — including the US Treasury, where workstations in OFAC and CFIUS were accessed. BeyondTrust detected anomalous behaviour on Dec 5, revoked the key on Dec 8, and the incident was publicly attributed to the Silk Typhoon group. Two lessons transfer directly to your Password Safe registrations. First: MFA would not have helped — an API key authenticates machine-to-machine, outside any interactive MFA path; machine credentials need their own controls. Second: one leaked key = every credential its runas can reach, so the scope of the runas user IS your blast radius.

Karthik at Flipkart faces this

API logs show successful SignAppin calls for runas flipkart\svc-ci-fetch from 203.0.113.77 at 03:14 — an address that belongs to no Flipkart office or runner.

Likely cause

A developer committed the 128-character API key to a public GitHub repo inside a helper script. The registration had no Authentication Rule, and the runas user held ISA on 40 accounts — worst-case scope.

Diagnosis

Repo secret-scan finds the key in git history; the registration audit shows zero IP Authentication Rules; role review shows ISA far beyond what the pipeline needs.

BeyondInsight > Configuration > General > API Registrations > svc-ci-fetch
Fix

Treat the old key as burned: regenerate it, add Authentication Rules pinning the CI runner IPs (10.20.30.0/24), demote the runas to Requestor on only the accounts the pipeline touches, and rotate every credential the old key could have read.

Verify

Replay SignAppin from an outside address — refused; from the runner subnet — 200. After 24 hours the API log shows runner IPs only, and the rotation report confirms every reachable credential changed.

Turn that into a standing routine. Scope: one registration per consumer (Jenkins ≠ GitLab ≠ the monitoring poller), each with its own runas holding the minimum role on the minimum accounts. Restrict: Authentication Rules on every registration — a key that only works from 10.20.30.41 is nearly worthless on GitHub. Rotate the keys themselves: regenerating a registration key is a console action on the API Registrations page — no engine rotates it for you, so put it on the calendar like a certificate renewal, and park the key in Secrets Safe so the handover is owned and audited. Watch: review which IPs sign in as your automation users; the Treasury incident was caught by exactly that kind of anomaly. The hotel master-key register is the right mental model — the master key exists, but every issue and return is written down, and a missing entry is itself the alarm.

Figure 4 — API quick-card — pin this above your desk
A dark reference card. Top: the base path slash BeyondTrust slash api slash public slash v3 and the PS-Auth header format with key, runas and optional pwd. Middle: eight endpoints — SignAppin, Signout, ManagedAccounts, Requests, Credentials by request ID, ISARequests, secrets-safe secrets by path and title, and secrets-safe text. Right: response codes 200, 201, 204, 401 with the 2FA challenge header, 403 and 404. Bottom: four amber hygiene rules — IP authentication rules, least-privilege runas, regenerate keys on a calendar, fetch per run and never cache. API quick-card — pin this above your desk BASE PATH https://HOST/BeyondTrust/api/public/v3 AUTH HEADER (every call) Authorization: PS-Auth key=…; runas=DOMAIN\user; pwd=[…]; pwd only if the registration demands it · 2FA: resend SignAppin with challenge=code THE 8 CALLS POST Auth/SignAppin → open session GET ManagedAccounts?… → find SystemId/AccountId POST Requests → returns RequestId GET Credentials/{id} → the secret PUT Requests/{id}/checkin → give it back POST ISARequests → ISA: secret in ONE call GET secrets-safe/secrets?path=…&title=… GET secrets-safe/secrets/{guid}/text · POST Auth/Signout RESPONSE CODES 200 OK · 201 created 204 no content (checkin) 401 + WWW-Authenticate-2FA = designed 2FA challenge, not an error 403 role / IP rule refused 404 wrong path or ID HYGIENE — ① IP Authentication Rules on every registration ② least-privilege runas ③ regenerate keys on a calendar (console action — nothing rotates them for you) ④ fetch per run, never cache refusedallowed
Everything from this lesson on one card: base path, header anatomy, the eight calls, the response codes that confuse beginners (401-2FA is a challenge, not an error), and the four hygiene rules.
🎮 Hands-on: BeyondTrust PAM Essentials room📼 Sibling lesson: Password Safe Session Management
👉 Full circle: scripts fetch fresh so the vault rotates nightly, team secrets live in owned safes, and the API key itself is scoped, IP-locked, calendar-rotated and watched. That is AAPM done properly — zero standing passwords, one accountable key.
Quick check · Q4 of 10

December 2024: a stolen Remote Support SaaS API key let attackers reach 17 customers, including the US Treasury. Which combination would most limit the same blast radius on YOUR Password Safe registration?

Correct: d. Blast radius = where the key works from × what its runas can reach × how long it stays valid — exactly the three controls in (d). (a) length does not matter once the key is copied; (b) MFA guards interactive humans, and the stolen key authenticated machine-to-machine outside that path; (c) rotating managed-account passwords does not stop a key that can simply fetch the new ones.

Pause & Predict

Predict: your CI stores the fetched password as a masked pipeline variable "to save API calls". What breaks, and exactly when? Type your guess.

Answer: It breaks at the next scheduled rotation — typically just after 23:30 UTC — because the cached value is now a stale hardcoded copy. Worse, masked variables leak: they appear in variable exports, debug reruns and forked-pipeline tricks. You have rebuilt section ①’s problem inside a shinier tool. Fetch per run, DurationMinutes 5, check in, sign out.

🤖 Ask the AI Tutor

Tap any question — instant, scoped to this lesson. No login, no waiting.

Pre-curated from BeyondTrust docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.

📝 Wrap-up assessment — six more

You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.

Q5 · Remember

What is the base path of the BeyondInsight / Password Safe public API?

Correct: a. The public API lives at https://<server>/BeyondTrust/api/public/v3 on-prem and cloud alike — it appears in every call this lesson made. The other three are plausible-looking inventions; if you typed them you would meet 404s, which is also why "404 = wrong path" sits on the cheat-card.
Q6 · Apply

Rahul’s Python job calls GET ManagedAccounts?systemName=PAY-DB-01&accountName=svc_corebank and receives 200 with an empty result, although the account exists in the console. What should he fix first?

Correct: d. GET ManagedAccounts only returns accounts that are API-enabled AND visible to the caller’s role — failing either gives a silent empty list, not an error. (a) is cargo-cult ops; (b) violates least privilege and changes nothing about API visibility; (c) ChangeTime is the rotation schedule, unrelated to account listing.
Q7 · Apply

Priya’s 02:00 unattended job at ICICI cannot wait for a human approver. Which call returns the credential in a single step, by design?

Correct: b. ISA (Information Systems Administrator) is the approval-less role: POST ISARequests returns the credential directly. (a) the Reason text never bypasses an approval chain — only the access policy decides auto-approval; (c) Credentials requires an existing approved RequestId; (d) check-in RETURNS a credential you already hold — it ends access, it does not grant it.
Q8 · Analyze

Why does runtime retrieval END the rotation deadlock, while a cached copy quietly restarts it?

Correct: c. The deadlock exists because standing copies and rotation cannot coexist. Fetch-fresh removes the standing copy, so rotation stops being scary — that is the entire architecture. (a) encryption strength is irrelevant to staleness; (b) it is actually slower by milliseconds, and that does not matter; (d) the API cannot know or police what a consumer does with the value — only your design can.
Q9 · Analyze

In the December 2024 Treasury incident, why would MFA on console logins NOT have stopped the attacker?

Correct: a. MFA protects a human at a login prompt; an API key never sees one — the Dec-2024 attackers used a stolen Remote Support SaaS key to act as the service itself. (b) and (c) describe failure modes that were not part of this incident; (d) is nonsense — the API is HTTPS-only, and transport security was never the issue. The transferable lesson: inventory and guard your machine credentials separately from human auth.
Q10 · Evaluate

ICICI’s architect must place three workloads: 600 rotating service-account passwords, 40 team-shared API tokens and certificates, and throwaway database creds for ephemeral test containers. Evaluate the best split.

Correct: b. Each store matches a lifecycle: rotating machine creds need the functional-account rotation engine (managed accounts); shared tokens/certs have no system to rotate against and need owned, audited shelves (Secrets Safe); minted-and-destroyed creds are what dynamic engines exist for. (a) breaks on the 40 tokens — no managed system exists; (c) throws away rotation for 600 accounts, recreating the stale-password problem at scale; (d) ignores that "free" means you now run, patch, audit and HA a second Tier-0 platform — and its root creds still need a vault.
Lesson complete — saved to your profile.
Almost! You need 70% (7 of 10) — re-read the path that tripped you up and tap "Try again".

🧠 In your own words

Type one line: Your manager asks: we already keep passwords in the Jenkins credentials store — why do we need AAPM and Secrets Safe? Then compare to the expert version.

Expert version:

Jenkins encrypts secrets at rest, but they are still standing copies: they never change when the vault rotates, any job that prints its environment can leak them, and nobody can answer who used what, when. With AAPM the job fetches the credential from Password Safe at runtime — so the vault rotates nightly and nothing breaks, every fetch is an audit row tied to a runas identity, and a stolen Jenkins export contains no passwords at all. Jenkins keeps exactly one secret: an IP-locked, least-privilege API key. Team-shared tokens and certificates that Jenkins never touches move to Secrets Safe, owned by a BeyondInsight group instead of a WhatsApp thread.

🗣 Teach a friend

Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.

📖 Glossary

AAPM
Application-to-Application Password Management — apps fetch credentials from the vault at runtime instead of storing their own copy.
Secret sprawl
The same credential copied across scripts, configs and pipelines until nobody can safely rotate it.
API registration
BeyondInsight object that issues a 128-character API key plus caller rules (source IP, password, 2FA).
PS-Auth
The API Authorization header scheme: key=…; runas=…; pwd=[…]; — key identifies the registration, runas the acting user.
runas
The Password Safe user an API call acts as; its roles decide exactly what that key can touch.
ISA
Information Systems Administrator role — instant, approval-free credential release via POST ISARequests.
Enable for API Access
Managed-account setting; without it the account is invisible to GET ManagedAccounts.
Secrets Safe
BeyondInsight store for team and application secrets — credentials, text, files — in safes and folders; no rotation engine.
Safe
A root folder in Secrets Safe; each team (a BeyondInsight group) gets its own isolated safe.
Check-in
Returning a released credential; with Change Password After Release set, the vault rotates it immediately.
OAuth client credentials
Alternative API auth for application-type users under an API Access Policy registration — token-based, no PS-Auth key header.

📚 Sources

  1. BeyondInsight & Password Safe API usage — PS-Auth header (key/runas/pwd), SignAppin/Signout, 2FA challenge, OAuth client credentials. docs.beyondtrust.com/bips/reference/beyondinsight-and-password-safe-api-usage
  2. Password Safe API endpoint catalog — Auth, ManagedAccounts, Requests, ISARequests, Credentials + defaults (120 / 525600 / 23:30). docs.beyondtrust.com/bips/v24.3/docs/password-safe-api
  3. Secrets Safe — teams as BeyondInsight groups, safes (root folders), folder paths, secret types. docs.beyondtrust.com/bips/docs/bi-cloud-secrets-safe
  4. BeyondInsight & Password Safe 25.2 release notes — client certificates deprecated as an API-registration auth method. docs.beyondtrust.com/bips/changelog/beyondinsight-and-password-safe-25-2-release-notes
  5. The Hacker News — Chinese APT exploits a stolen BeyondTrust Remote Support SaaS API key, 17 customers affected (Dec 2024). thehackernews.com/2024/12/chinese-apt-exploits-beyondtrust-api.html
  6. NVD — CVE-2024-12356, CVSS 9.8 unauthenticated command injection in RS/PRA (same advisory window as the API-key incident). nvd.nist.gov/vuln/detail/cve-2024-12356
  7. BeyondInsight & Password Safe public Postman workspace — ready-made API collection for lab practice. postman.com/zerotrust-how-community/beyondtrust-community-s-public-workspace/collection/csvyn8n/beyondinsight-and-password-safe-api
  8. KuppingerCole Enterprise Secrets Management Leadership Compass 2025 — BeyondTrust positioning among 16 vendors. beyondtrust.com/press/kuppingercole-leadership-compass-secrets-management

What's next?

Your vendor’s engineer still rides a VPN straight into the LAN to fix one server — full network access for a one-task visit. Next lesson: Privileged Remote Access — brokered, recorded, outbound-443 vendor sessions with no VPN client and no inbound firewall holes.