Mobile Credentials & Vault
Vendor device farms (BrowserStack, Sauce Labs) require API credentials. The platform stores these once in the environment credential vault — encrypted at rest with AES-GCM — and references them from mobile apps by tag. No secrets in JSON.
This page covers the vendor-credential side. For app-login credentials (the usernames + passwords your test types into the app), see the test data section in YAML format.
The model
Environment
│
└─ auth_config (jsonb, AES-GCM encrypted)
│
├─ defaultCredentialTag: "prod-browserstack" ← env-level default
│
└─ credentials: [
{
tag: "prod-browserstack",
username: "alice@company.com",
password: "ABCDEF...", ← BrowserStack access key
isDefault: true
},
{
tag: "dev-saucelabs",
username: "company-dev",
password: "12345...",
isDefault: false
}
]
Mobile App
│
├─ cloud_provider: "BROWSERSTACK"
└─ cloud_config: { "credentialTag": "prod-browserstack", "deviceName": "Pixel 7" }
At job time the agent calls a vault-resolution endpoint with the mobile app id, the backend walks MobileApp → EnvironmentTarget → Environment → vault, finds the credential by tag, and returns the inline userName + accessKey to the agent.
Adding a vendor credential
In the environment's auth config, add a credential set:
{
"defaultCredentialTag": "prod-browserstack",
"credentials": [
{
"tag": "prod-browserstack",
"username": "your-bs-username",
"password": "your-bs-access-key",
"isDefault": true
}
]
}
Fields:
- tag — referenced from
cloud_config.credentialTag. Pick anything memorable (prod-bs,qa-saucelabs,team-a-bs). - username — BrowserStack/Sauce Labs username (typically your dashboard login email).
- password — the vendor access key. Field is named
passwordfor compatibility with the rest of the vault, but for vendor APIs it's the access key. - isDefault — optional. When true, any app that doesn't specify
credentialTaguses this one.
The password is AES-GCM encrypted before being written to the DB via the AuthConfigEntityListener.
Three reference patterns
1. Explicit tag (most explicit)
{
"credentialTag": "prod-browserstack",
"deviceName": "Google Pixel 7"
}
Wins over any default. Use when an app needs different credentials from the env default (e.g., a customer account vs. an internal account).
2. Default credential (implicit)
{
"deviceName": "Google Pixel 7"
}
No credentialTag, no inline creds. Agent uses the env's default credential — either:
- The credential matching
environment.defaultCredentialTag, OR - Any credential with
isDefault: true
Most teams set one default per env so 90% of mobile apps don't need credential boilerplate.
3. Inline (legacy, back-compat)
{
"userName": "your-bs-username",
"accessKey": "your-bs-access-key",
"deviceName": "Google Pixel 7"
}
Passes through unchanged. The vault is bypassed. Avoid for new apps — secrets in JSON are harder to rotate and surface in any UI roundtrip that re-fetches the mobile app.
Precedence
| What you set | What the agent receives |
|---|---|
Inline userName + accessKey | Inline (no vault lookup) |
credentialTag + matching vault entry | Resolved from vault |
credentialTag + missing vault entry | Hard error — names the missing tag, lists available ones |
| No tag, no inline, but env has a default | Resolved from env default |
| No tag, no inline, no env default | Pass through; vendor validation surfaces "missing userName" |
Rotating a credential
Update the vault entry's password field. Every mobile app pointing at that tag picks up the change on the next job — no app-level edit needed. Old in-flight tests continue with the old credential since the agent caches it for the duration of the test.
To force immediate rotation across all running tests: remove the old credential set, the next vault-resolution call fails fast, the agent surfaces an error in Job History, operator fixes.
Multiple environments
The vault is per-environment, so:
| Environment | Credential tag | Use case |
|---|---|---|
| Development | dev-browserstack | shared low-volume tier |
| Staging | staging-browserstack | mid-volume parallel tests |
| Production | prod-browserstack | release-day matrix |
Same mobile app can reference different tags depending on which environment it's targeting. The agent resolves against the environment that owns the EnvironmentTarget linked to the test job.
Secrets at runtime
After resolution, the inline creds exist only:
- In-memory on the backend (during the single resolve call)
- Over the wire from backend → agent (HTTPS in production)
- In-memory on the agent (during driver creation)
They are not stored on disk, not in the agent's job-claim cache, not in MinIO. Each job re-resolves from the vault.
Audit trail
The vault already audits reads via CredentialAuditService. Every time a job resolves a tag, an audit entry is written: who (agent id) + when + which tag + which environment. View via the existing audit log endpoint or the Audit Logs UI page.
What's NOT in the vault yet
- Per-app credential overrides — today's pattern is env-scoped. Some teams might want per-app credentials (e.g., team-A uses tag X, team-B uses tag Y in the same env). Today the workaround is multiple environments. Per-app tags are on the roadmap.
- Credential rotation hooks — no UI for time-bound rotation policies. Manual rotation only.
Related
- Device farms — where credentials get used (BrowserStack / Sauce)
- Self-hosted — T2 doesn't need vendor credentials at all
- API credentials — same vault, different consumers