Skip to content

Security

GPC handles sensitive credentials (service account keys, OAuth tokens) and interacts with production app releases. This page documents the threat model, security controls, and best practices.

Threat Model

Assets to Protect

AssetSensitivityRisk if Compromised
Service account keysCriticalFull API access to all apps in the account
OAuth tokensHighUser-scoped access, time-limited
API responsesMediumMay contain PII (reviews, user emails)
Upload artifactsMediumAAB/APK files (intellectual property)
Configuration filesLowMay reference credential paths

Attack Vectors

VectorRiskMitigation
Credential committed to repoHigh.gitignore templates, gpc doctor warnings
Credential leaked in logsHighRedaction in verbose/debug output
Token theft from diskMediumOS keychain storage, file permissions (0600)
Man-in-the-middleLowTLS-only, custom CA certificate support
Malicious pluginMediumPlugin permission scoping, approval flow
Dependency supply chainMediumLockfile, audit, minimal dependencies

Credential Storage

Service Account Keys

Service account keys grant full API access and are long-lived. GPC never copies, moves, or embeds key content.

EnvironmentStorage MethodNotes
CI/CDEnvironment variable or mounted secretGPC_SERVICE_ACCOUNT (JSON string or file path)
Local devFile path reference in configNever copied into GPC storage
DockerMounted volume or env varNever baked into image

Rules enforced by GPC:

  • Config stores only the path to the key file, never the key content
  • gpc doctor warns if the key file has overly permissive permissions (>0600)
  • gpc doctor warns if the key file is inside a git repository

Token Cache Security

Service account access tokens are cached in two layers:

  1. In-memory cache -- fastest lookup, cleared when the process exits
  2. Filesystem cache -- ~/.cache/gpc/token-cache.json with 0600 permissions in a 0700 directory

Security properties:

  • Atomic writes via temp file + rename (no partial reads)
  • Promise-based mutex prevents race conditions in concurrent requests
  • Cache key validation enforces email format only -- prevents path traversal
  • 5-minute safety margin before expiry triggers proactive refresh

OAuth Tokens

PlatformStorage Location
macOSKeychain (security CLI)
Linuxlibsecret / gnome-keyring / encrypted file fallback
WindowsWindows Credential Manager
CI/headlessNot applicable -- use service account

Fallback: If no OS keychain is available, tokens are stored in ~/.config/gpc/credentials.json with 0600 permissions and a warning on first use.

Token lifecycle:

  1. OAuth device flow produces access + refresh tokens
  2. Access token cached (1-hour TTL)
  3. On expiry, auto-refreshed using refresh token
  4. On gpc auth logout, token is revoked and deleted from storage
  5. Refresh token rotation enforced when supported

Secrets Redaction

All output layers (human, JSON, YAML, debug logs) pass through a redaction filter before reaching any output destination.

Redacted Patterns

PatternExample InputRedacted Output
Service account key ID"private_key_id": "abc123...""private_key_id": "[REDACTED]"
Private key content-----BEGIN PRIVATE KEY-----[REDACTED_KEY]
OAuth access tokensya29.a0AfH6SM...ya29.[REDACTED]
Refresh tokens1//0eXy...1//[REDACTED]
Client secret"client_secret": "GOCSPX-...""client_secret": "[REDACTED]"
Client email"client_email": "sa@proj.iam..."Shown (needed for debugging)

Redaction Architecture

┌─────────────────────┐
│   Command Output    │
└──────────┬──────────┘

    ┌──────▼──────┐
    │  Redaction   │  <- Applied before ANY output
    │   Filter     │     (console, file, JSON)
    └──────┬──────┘

    ┌──────▼──────┐
    │  Formatter   │  <- Human / JSON / YAML
    └─────────────┘

Redaction is applied before formatting and cannot be disabled.

Debug Mode

  • --verbose shows request URLs and response headers (auth header redacted)
  • Request/response bodies containing credentials are never logged
  • GPC_DEBUG=1 enables full debug output but still applies redaction

File Permissions

Files Created by GPC

FilePermissionsContents
~/.config/gpc/config.json0644Non-sensitive configuration
~/.config/gpc/credentials.json0600OAuth tokens (keychain fallback)
~/.config/gpc/profiles/0700Profile directories
~/.config/gpc/audit.log0600Audit log (JSON Lines)

Files Validated by GPC

FileExpected PermissionsAction if Wrong
Service account key0600Warning via gpc doctor
Credentials file0600Auto-fix + warning
Config directory0700Warning via gpc doctor

Network Security

TLS

  • All API communication is over HTTPS (enforced by the googleapis client library)
  • No HTTP fallback exists
  • Custom CA certificates are supported via GPC_CA_CERT or NODE_EXTRA_CA_CERTS

Proxy Support

ConfigurationMethod
Environment variableHTTPS_PROXY or https_proxy
ExclusionsNO_PROXY for bypass rules
Config fileproxy: "https://proxy.corp:8080"
With authenticationhttps://user:pass@proxy:8080

Client-Side Rate Limiting

BucketDefault LimitProtects Against
General API50 req/sExceeding 3,000/min quota
Reviews GET3 req/sExceeding 200/hour quota
Reviews POST1 req/sExceeding 2,000/day quota
Voided purchases1 req/sExceeding 30/30s burst quota

Input Validation

CLI Arguments

InputValidationReject Example
Package namesAndroid format: [a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+invalid
File pathsExistence and type check before upload/nonexistent/file.aab
Track namesKnown tracks + custom track formatunknown-track
Language codesBCP 47 validationxx-YY
ArgumentsNo shell expansion passed to API--

File Uploads

CheckValidationLimit
File typeMagic bytes validation (AAB/APK)--
File sizeSize check against Play Console limits150MB (APK/AAB)
Mapping filesProGuard/R8 format validation--

Config Files

  • Schema validation on load with clear error messages
  • Unknown keys trigger warnings (not errors) for forward compatibility
  • Environment variable values are validated the same as config values
  • Prototype pollution prevention -- __proto__, constructor, and prototype are rejected as config keys
  • Unsafe keys are fully deleted from parsed config (not set to undefined)

Filesystem Operations

  • Fastlane metadata directory reads validate language folder names against ^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$ to prevent path traversal
  • Upload responses are validated before use -- null/missing data from the API triggers a clear error

Plugin Security

Trust Model

Plugin TypePatternTrust Level
First-party@gpc-cli/plugin-*Auto-trusted, no permission checks
Third-partygpc-plugin-* or config pathUntrusted, permissions validated

Permission Enforcement

Third-party plugins declare required permissions in PluginManifest:

typescript
type PluginPermission =
  | "read:config"
  | "write:config"
  | "read:auth"
  | "api:read"
  | "api:write"
  | "commands:register"
  | "hooks:beforeCommand"
  | "hooks:afterCommand"
  | "hooks:onError"
  | "hooks:beforeRequest"
  | "hooks:afterResponse";

Rules:

  1. Plugins cannot access credentials directly
  2. Third-party plugins require explicit user approval on first run
  3. Unknown permissions throw PLUGIN_INVALID_PERMISSION (exit code 10)
  4. Error handlers in plugins are wrapped -- a failing handler cannot crash GPC
  5. Plugin approval can be revoked: gpc plugins revoke <name>

CI/CD Security Guidelines

Secrets Management

yaml
# GitHub Actions -- recommended pattern
env:
  GPC_SERVICE_ACCOUNT: ${{ secrets.GPC_SERVICE_ACCOUNT }}

Do NOT

  • Hardcode credentials in workflow files
  • Echo or print credential values in CI logs
  • Store credentials as build artifacts
  • Use credentials from forks in pull request workflows

Least Privilege

  • Create dedicated service accounts per CI environment (staging vs production)
  • Grant only required Play Console permissions (e.g., "Release manager" not "Admin")
  • Rotate service account keys on a regular schedule
  • Use short-lived credentials where possible (Workload Identity Federation)

Audit Trail

  • --json output includes command name, timestamp, and auth identity
  • CI plugins can emit structured logs for SIEM ingestion
  • All write operations are logged with before/after state
  • Use --dry-run in PR checks to validate without modifying state

Dependency Policy

Rules

  1. Minimize dependency count -- prefer Node.js built-ins
  2. Pin major versions in package.json
  3. pnpm audit runs in CI on every pull request
  4. Dependabot enabled for security updates
  5. No postinstall scripts in production dependencies

Approved External Dependencies

PackagePurposeJustification
google-auth-libraryAuth strategiesOfficial Google library
commanderCLI frameworkIndustry standard, battle-tested
chalkTerminal colorsCross-platform color support
oraSpinnersTerminal animation handling
cli-table3Table outputColumn alignment, wrapping

New dependencies are reviewed for: maintenance status, download count, transitive dependency count, and license compatibility (MIT, Apache-2.0, BSD preferred).

Security Checklist

Before Release

  • [ ] No credentials in source code or test fixtures
  • [ ] All user inputs validated before API calls
  • [ ] Secrets redacted in all output modes (human, JSON, YAML, debug)
  • [ ] File permissions set correctly on credential files
  • [ ] pnpm audit shows no high/critical vulnerabilities
  • [ ] Plugin permission model enforced
  • [ ] Error messages do not leak sensitive information
  • [ ] TLS enforced for all network communication

For Contributors

  • [ ] Never commit real service account keys (use fixtures with fake data)
  • [ ] Test fixtures use obviously fake credentials
  • [ ] New dependencies reviewed for security posture
  • [ ] No eval(), Function(), or dynamic require() of user input
  • [ ] No shell command construction from user input

Released under the MIT License.