# Profiles And Credentials

> Use when authoring or modifying any claude-team-toolkit skill that loads credentials, switches between accounts/orgs/environments, or needs confirmation for destructive operations. Reference for the shared profile + ctt_* helper pattern used by all credential-bearing skills.

- Skill: `tuannv14/profiles-and-credentials` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tuannv14/profiles-and-credentials`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tuannv14/profiles-and-credentials/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tuannv14 (https://skillmd.com/u/tuannv14)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tuannv14/profiles-and-credentials

---


# Profiles & Credentials — shared pattern reference

Reference skill describing the credential / profile / confirmation pattern used by all claude-team-toolkit skills (azure-devops, fastlane, firebase, heroku, k6, maestro, postgres, rspec, sentry, shopify, slack, trello, etc.). Implementation lives in [`lib/credentials.sh`](../../lib/credentials.sh) and [`lib/confirm.sh`](../../lib/confirm.sh).

## Overview

All credential-bearing skills share a uniform pattern:

- INI credentials at `~/.<service>/credentials` (mode 600)
- Multiple `[profile]` sections per file (e.g., `[default]`, `[work]`, `[prod]`)
- Resolution order: `--profile <name>` flag → `<SERVICE>_PROFILE` env → `~/.<service>/active_profile` → `[default]`
- Helper sourced from `lib/credentials.sh` populates `CTT_<KEY>` env vars
- Destructive ops gated by `ctt_confirm` from `lib/confirm.sh`
- Actions audit-logged to `~/.claude-team-toolkit/audit.log` (key NAMES only, never values)

## When to Use

- Authoring a NEW skill that needs per-account/per-org/per-env switching
- Modifying credential loading in an existing skill
- Adding destructive operations that need typed confirmation
- Debugging why a skill picks the wrong profile
- Reviewing security posture of a skill

## When NOT to Use

- One-off scripts not meant for reuse → just hardcode and move on
- Skills with no credentials (e.g., `react-native`, `rails-security`) → nothing to load
- Cross-cloud unified credential systems → use the cloud's native config (`~/.aws/credentials`, etc.) instead of reinventing

## Profile file shape

```ini
# ~/.<service>/credentials  (mode 600)
[default]
field_a = value
field_b = secret

[other-profile]
field_a = different
require_confirm = true        # gate every mutation in this profile
```

After `ctt_load_creds <service> [profile]`:
- `CTT_PROFILE=<resolved>` (e.g., `default`)
- `CTT_FIELD_A=<value>`, `CTT_FIELD_B=<secret>` (uppercased, hyphens → underscores)

Each load **clears every `CTT_*` variable** before populating the new ones,
except the control variables `CTT_NONINTERACTIVE` and `CTT_HOME`. A field
present in the old profile but absent from the new one is therefore unset, not
inherited — without that, switching profiles would silently carry
`default_team`, `org`, `project`, `default_app`, `database`, `read_only` or
`require_confirm` across accounts, and across services. The names come from
the shell (`compgen -v CTT_`), not from a tracking variable: a tracking
variable is state a hostile environment can pre-set.

Two boolean fields are normalised at load: `read_only` and `require_confirm`
become the literal `true` for any of `true`, `yes`, `1`, `on` (case-insensitive)
and `false` otherwise, so `[ "$CTT_READ_ONLY" = "true" ]` is the correct test in
every skill and `read_only = yes` cannot fail open.

`ctt_save_profile` refuses profile names outside `[A-Za-z0-9_-]`, field names
outside `[a-zA-Z0-9_-]`, and any value containing a newline — each of those
could otherwise write a second `[section]` into the INI file. `ctt_load_creds`
applies the same name rule to the resolved profile, because the name is used in
a grep pattern. `ctt_audit_log` replaces newlines and tabs in the action text so
an id or team key cannot forge a second audit record.

Consequence for skill authors: read every field as `${CTT_FIELD:-}` and treat
unset as "not configured on this profile". Never assume a field survives a
profile switch.

## Helper API (from `lib/credentials.sh`)

| Function | Purpose |
|---|---|
| `ctt_load_creds <svc> [profile]` | Clear the previous profile's fields, then resolve + load this one into `CTT_*` env |
| `ctt_save_profile <svc> <profile> <k=v>...` | Atomic INI section write |
| `ctt_list_profiles <svc>` | List sections, mark active with `*` |
| `ctt_use_profile <svc> <profile>` | Set active profile pointer |
| `ctt_active_profile <svc>` | Print resolved active profile |
| `ctt_remove_profile <svc> <profile>` | Refuses to remove last profile |
| `ctt_mask <value>` | Returns `****<last4>` for safe logging |
| `ctt_validate_perms <file>` | Refuses non-600/400 mode (POSIX only) |
| `ctt_audit_log <svc> <action>` | Append timestamp + profile + action |

## Confirmation API (from `lib/confirm.sh`)

```bash
ctt_confirm "Delete app foo?" || return 1                # y/N prompt
ctt_confirm "Drop table users?" "DELETE" || return 1      # typed phrase
ctt_warn_destructive "Action description"                # noisy stderr banner
```

`CTT_NONINTERACTIVE=1` auto-denies all confirmations (CI safety).

## Common Mistakes

- Reading credentials directly with `awk` instead of `ctt_load_creds` → bypasses perm validation and the cross-profile clearing
- Assuming a field persists across a profile switch → it is cleared on every load. Read `${CTT_FIELD:-}`, not `$CTT_FIELD`.
- Logging full credential values instead of `ctt_mask` → secrets leak to terminal/CI logs
- Using `eval` to set vars from credentials → injection risk. Helper uses `printf -v` for safety.
- Audit log records values not just key names → audit log itself becomes a secret store
- Confirmation without typed phrase for irreversible ops → "y" is too easy a typo
- Service name with uppercase or special chars → helper validates `^[a-z0-9-]+$`

## Security defaults

- Credentials directory: `chmod 700`. File: `chmod 600`. Helper refuses to load otherwise (POSIX).
- Windows: best-effort (NTFS ACLs not enforced by helper) — document in skill's safety section.
- `CTT_NONINTERACTIVE=1` in CI → auto-deny destructive ops (no surprise mutations).
- Audit log at `~/.claude-team-toolkit/audit.log` (mode 600). Records: timestamp, service, profile, action.
- Never write credentials into git, chat output, or any file outside `~/.<service>/credentials`.

