This document describes the security invariants maintained by the Basecamp SDK across all five implementations (Go, TypeScript, Ruby, Swift, Kotlin).
All SDK implementations enforce HTTPS for API communication with specific exceptions for local development:
| Context | HTTPS Required | Localhost Exception |
|---|---|---|
| Base URLs | Yes | Yes - for local dev/testing |
| OAuth endpoints | Yes | Yes - for local OAuth testing |
| Webhook payload URLs | Yes | No - webhooks are production-only |
Localhost is defined as: localhost, 127.0.0.1, or ::1.
Rationale: Base URLs and OAuth endpoints may use localhost during development. Webhook payload URLs never allow localhost because webhooks are a server-to-server feature that only makes sense in production contexts.
Authorization headers are automatically stripped when HTTP redirects cross origin boundaries. This prevents credential leakage to third-party hosts.
Link headers from paginated responses are validated for same-origin before following. This prevents:
- SSRF attacks via poisoned Link headers
- Token leakage to attacker-controlled servers
Cache keys include a hash of the authorization token to isolate cached responses per-credential. This prevents:
- Cross-user cache poisoning
- Stale responses after token refresh
| Context | Limit | Purpose |
|---|---|---|
| General responses | 50 MB | Prevent memory exhaustion from large payloads |
| Error bodies | 1 MB | Limit parsing overhead for error responses |
| OAuth token responses | 1 MB | Prevent DoS during authentication |
| Error messages | 500 chars | Prevent information leakage in logs/errors |
Error messages extracted from API responses are truncated to 500 characters before being included in exceptions. This prevents:
- Sensitive data in error messages from being logged
- Unbounded memory growth from malformed error responses
All SDK clients are safe for concurrent use after construction. Thread/goroutine safety guarantees:
ClientandAccountClientare safe for concurrent useAuthManageruses mutex protection for all credential operations- Service accessors are protected by per-AccountClient mutex
- Service accessors use nullish coalescing for atomic initialization
- Token hash computation uses promise coalescing to prevent duplicate crypto operations
- ETag cache uses Map for thread-safe (single-threaded JS) access
OauthTokenProvideruses mutex for token refresh operations- The
refreshmethod holds mutex during the entire check-and-refresh operation
BasecampClientandAccountClientare markedSendablefor Swift 6 strict concurrency- All service properties are safe for concurrent access via actor isolation
- Configuration is immutable (
letproperties onBasecampConfig)
BasecampClientis safe for concurrent use from coroutines- Ktor's
HttpClienthandles connection pooling and thread safety internally - Configuration is immutable (
valproperties onBasecampConfigdata class)
Important: Do not modify configuration after creating a client. Configuration is captured at construction time.
Breaking Change (Go): Client.Config() now returns Config by value instead of *Config pointer. This prevents post-construction modification but may require code changes if callers expected pointer semantics.
Go, TypeScript, Ruby, and Kotlin SDKs provide helper utilities for OAuth 2.0 PKCE (Proof Key for Code Exchange):
// Go
pkce, err := oauth.GeneratePKCE()
// pkce.Verifier, pkce.Challenge
state, err := oauth.GenerateState()// TypeScript
const pkce = await generatePKCE();
// pkce.verifier, pkce.challenge
const state = generateState();# Ruby
pkce = Basecamp::Oauth::Pkce.generate
# pkce[:verifier], pkce[:challenge]
state = Basecamp::Oauth::Pkce.generate_state// Kotlin
val pkce = Pkce.generate()
// pkce.verifier, pkce.challenge
val state = Pkce.generateState()Security properties:
- Verifiers are 43 characters (32 random bytes, base64url-encoded)
- Challenges are SHA256 hashes of verifiers (use
code_challenge_method=S256) - State parameters are 22 characters (16 random bytes, base64url-encoded)
- All use cryptographically secure random number generators
Go, TypeScript, and Ruby SDKs provide utilities to safely log HTTP requests without exposing credentials:
// Go
safeHeaders := basecamp.RedactHeaders(req.Header)
logger.Info("request", "headers", safeHeaders)// TypeScript
const safeHeaders = redactHeaders(response.headers);
console.log("Response headers:", safeHeaders);# Ruby
safe = Basecamp::Security.redact_headers(headers)
logger.info("Headers: #{safe}")Redacted headers: Authorization, Cookie, Set-Cookie, X-CSRF-Token
The SDKs implement safe retry behavior:
- GET requests: Automatically retried with exponential backoff on 429/503
- Mutations (POST/PUT/DELETE): NOT automatically retried on 429/503 to prevent duplicate operations
- 401 responses: Token refresh attempted, then single retry for all methods
- Retry-After headers: Respected for 429 responses
If you discover a security vulnerability, please report it through Basecamp's security page or email security@basecamp.com rather than opening a public issue. You can also use GitHub Security Advisories to report privately.