Skip to content

Secrets & Credentials

Zero-trust policy: no secret value is ever committed to git. All secrets are stored encrypted. This document describes what each secret is, where it lives, and how to regenerate it.


Storage Locations

LocationWhat goes thereEncrypted?
macOS KeychainDeveloper credentials (PATs, tokens used locally)✅ Yes (login keychain)
Woodpecker SecretsCI/CD secrets injected into pipeline steps✅ Yes
Cloudflare Worker SecretsRuntime secrets for API workers✅ Yes
~/.zshenvKeychain lookup commands only — never raw values

Developer Credentials (macOS Keychain)

Woodpecker CI Personal Access Token

FieldValue
Keychain servicewoodpecker-ci-lucidpal
Keychain accountwmehanna
Loaded as$WOODPECKER_TOKEN via ~/.zshenv
Regenerate athttps://ci.lucidpal.app/userPersonal Access Token

Store:

bash
security add-generic-password \
  -a "wmehanna" \
  -s "woodpecker-ci-lucidpal" \
  -w "<token>" \
  -T ""

~/.zshenv entry:

bash
export WOODPECKER_TOKEN=$(security find-generic-password -a "wmehanna" -s "woodpecker-ci-lucidpal" -w 2>/dev/null)

Woodpecker CI Secrets

Configure at: https://ci.lucidpal.app → lucid-fabrics/lucidpal-dev → Settings → Secrets

Apple / TestFlight

| Secret | Description | Regenerate at | | ----------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | APP_STORE_CONNECT_API_KEY_ID | ASC API Key ID (e.g. 9W74KCHBGG) | App Store Connect → Users & Access → Integrations → App Store Connect API | | APP_STORE_CONNECT_API_ISSUER_ID | ASC API Issuer ID (UUID) | Same page as above | | APP_STORE_CONNECT_API_KEY_CONTENT | Base64-encoded .p8 private key | Same page — download .p8, then base64 -i AuthKey_<ID>.p8 | | MATCH_PASSWORD | Encryption password for match cert repo | Stored in team memory — do not rotate without re-encrypting the match repo | | MATCH_GIT_BASIC_AUTHORIZATION | Base64 user:token for match git repo access | echo -n "user:ghp_token" | base64 — regenerate GitHub PAT at https://github.com/settings/tokens |

Cloudflare

SecretDescriptionRegenerate at
CLOUDFLARE_API_TOKENPages Write + Workers Scripts Write + Workers Routes WriteCloudflare Dashboard → My Profile → API Tokens
CLOUDFLARE_ACCOUNT_IDeab661fba6c6a4d2e11c090abd0a811bNot rotatable — fixed account ID

Cloudflare Worker Secrets

Set via wrangler secret put <NAME> (dev) or wrangler secret put <NAME> --env production (prod).

lucidpal-api (dev — api-dev.lucidpal.app)

SecretDescriptionRegenerate at
LICENSING_API_URLDev licensing service base URLInternal — https://api-dev.lucidfabrics.com
LICENSING_API_KEYX-Admin-Key header for licensing-serviceLicensing-service admin — check lucidfabrics secrets
LICENSING_PRODUCT_IDProduct identifier for lucidpal devlucidpal-dev-001

APNs (silent sync push) — dev

SecretDescriptionRegenerate at
APNS_KEY_IDAPNs auth key ID (10-char, e.g. ABC123DEFG)Apple Developer → Keys
APNS_TEAM_IDApple Developer Team ID (10-char)Apple Developer → Membership details
APNS_PRIVATE_KEY_BASE64Base64-encoded .p8 private key for APNs JWT signingDownload .p8 from same Keys page, then base64 -i APNs_*.p8
APNS_BUNDLE_IDApp bundle ID — app.lucidpalFixed — matches CFBundleIdentifier in Info.plist

All four APNs secrets are optional. Omitting them disables silent push without affecting sync functionality. The same key works for dev (sandbox) and prod (production) — endpoint selection is determined by the environment field stored in device_push_tokens.

lucidpal-api-production (prod — api.lucidpal.app)

SecretDescriptionRegenerate at
LICENSING_API_URLhttps://api.lucidfabrics.comInternal
LICENSING_PRODUCT_IDlucidpal-prod-001Internal

APNs (silent sync push) — prod

Same four secrets as dev (APNS_KEY_ID, APNS_TEAM_ID, APNS_PRIVATE_KEY_BASE64, APNS_BUNDLE_ID). Use the same key — the worker routes to api.push.apple.com (production) vs api.sandbox.push.apple.com based on the environment column in device_push_tokens.


Woodpecker Server Config (LXC 120)

Not rotated unless the server is rebuilt. Stored in /opt/woodpecker/docker-compose.yml on pve-mirna LXC 120.

SecretDescriptionRegenerate
WOODPECKER_AGENT_SECRETShared secret between server and agentsopenssl rand -hex 32 — update in Compose + macOS agent plist + restart both
WOODPECKER_GITHUB_CLIENTGitHub OAuth App client ID (Ov23liVtWoZtHzqP7PzN)GitHub → Settings → Developer settings → OAuth Apps → LucidPal CI
WOODPECKER_GITHUB_SECRETGitHub OAuth App client secretSame page — regenerate client secret

Stripe (via Licensing-service)

Stripe keys live in the lucidfabrics licensing-service worker secrets, not in this repo. Documented here for reference.

KeyEnvironmentRegenerate at
sk_test_*Sandbox (dev)Stripe Dashboard → Developers → API keys
sk_live_*ProductionStripe Dashboard → Developers → API keys
Webhook secretPer-endpointStripe Dashboard → Developers → Webhooks

Rotation Checklist

When rotating any secret:

  1. Generate new value at the source (links above)
  2. Update in the storage location (Keychain / Woodpecker / Cloudflare)
  3. For MATCH_PASSWORD: re-encrypt the match repo before rotating
  4. For WOODPECKER_AGENT_SECRET: update Compose file + macOS agent plist + restart both
  5. Verify the next pipeline run succeeds before discarding the old value

Internal — not for distribution