Severity: High
Area: backend/auth
File: acbu-backend/src/services/auth/authService.ts (getPlaceholderStellarAddress)
Impact: Non-G placeholders can break downstream Stellar validation and UX.
Acceptance Criteria: No user row ships with invalid stellarAddress format in prod.
The original implementation had several gaps:
- No Database Constraint: The
users.stellar_addresscolumn had no CHECK constraint to prevent invalid addresses from being inserted - No Placeholder Detection: While wallet creation used
assertValidStellarAddress(), there was no protection against placeholder/test addresses like "GTEST...", "GDUMMY...", etc. - No Validation on Read: When reading stellar addresses, there was no validation to catch corrupted/invalid data
- Test Pollution: Test files used invalid placeholder addresses that could accidentally leak into production
function isPlaceholderAddress(address: string): boolean {
if (!address || address.length !== 56) return true;
const placeholderPatterns = [
/^G[A]{55}$/, // All A's (GAAAA...)
/^G[B]{55}$/, // All B's (GBBBB...)
/^G[0]{55}$/, // All zeros
/^GTEST/, // Starts with GTEST
/^GDUMMY/, // Starts with GDUMMY
/^GPLACEHOLDER/, // Starts with GPLACEHOLDER
/^GXXXXXXXX/, // Starts with GXXXXXXXX
];
return placeholderPatterns.some(pattern => pattern.test(address));
}- Added validation before persisting generated addresses
- Rejects addresses that match placeholder patterns
- Logs errors for debugging
- Added validation of stored address format (defense in depth)
- Throws 500 error if database contains invalid address
- Logs critical errors for investigation
- Single entry point for externally setting stellar addresses
- Strict validation with both format and placeholder checks
- Proper error handling and logging
2. Database Migration (prisma/migrations/20260426000000_add_stellar_address_validation/migration.sql)
DO $$
DECLARE
invalid_count INTEGER;
BEGIN
SELECT COUNT(*) INTO invalid_count
FROM users
WHERE stellar_address IS NOT NULL
AND (invalid format conditions...);
IF invalid_count > 0 THEN
RAISE EXCEPTION 'Found % users with invalid stellar_address format...', invalid_count;
END IF;
END $$;ALTER TABLE users
ADD CONSTRAINT chk_valid_stellar_address
CHECK (
stellar_address IS NULL
OR (
-- Valid format: 56 characters, starts with G, base32 characters only
LENGTH(stellar_address) = 56
AND stellar_address LIKE 'G%'
AND stellar_address ~ '^[A-Z2-7]{56}$'
-- Not a placeholder
AND stellar_address !~ '^G[A]{55}$'
AND stellar_address !~ '^G[B]{55}$'
AND stellar_address !~ '^G[0]{55}$'
AND stellar_address NOT LIKE 'GTEST%'
AND stellar_address NOT LIKE 'GDUMMY%'
AND stellar_address NOT LIKE 'GPLACEHOLDER%'
AND stellar_address NOT LIKE 'GXXXXXXXX%'
)
);CREATE INDEX IF NOT EXISTS idx_users_stellar_address_not_null
ON users (stellar_address)
WHERE stellar_address IS NOT NULL;A standalone script to validate existing data before applying migration:
npx ts-node prisma/validateStellarAddresses.tsFeatures:
- Scans all users with stellar addresses
- Validates format, length, and checksum
- Detects placeholder patterns
- Provides detailed report of invalid addresses
- Suggests remediation options
Added comments to document the constraint:
model User {
stellarAddress String? @unique @db.VarChar(56)
// B-013: stellarAddress is validated at application level and database constraint
// Valid addresses: 56 chars, starts with 'G', base32 encoded, not a placeholder
...
}| Layer | Protection | Location |
|---|---|---|
| Application | assertValidStellarAddress() |
src/utils/stellar.ts |
| Application | isPlaceholderAddress() |
src/services/wallet/walletService.ts |
| Application | Validation on read | assertUserWalletAddress() |
| Database | CHECK constraint | Migration SQL |
| Database | Length constraint | @db.VarChar(56) |
| Database | Uniqueness constraint | @unique |
-
Run validation script:
npx ts-node prisma/validateStellarAddresses.ts
-
Fix any invalid addresses (if found):
-- Option 1: Clear invalid addresses UPDATE users SET stellar_address = NULL WHERE id = 'user-id'; -- Option 2: Replace with valid address (if user has real wallet) UPDATE users SET stellar_address = 'GVALID...' WHERE id = 'user-id';
-
Apply migration:
npx prisma migrate deploy
- Monitor logs for any "Invalid stellar address format in database" errors
- Verify new user wallets are created successfully
- Test signin flow to ensure wallet creation works
The wallet service tests use valid Stellar addresses:
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF(valid test address)- Avoid using invalid placeholders like
GSTELLARorGSTELLAR2
- Test user signup → signin → wallet creation flow
- Verify stellar address is valid after wallet creation
- Test address validation on transfer operations
// Should succeed
const validAddress = "G" + randomValidStellarAddress();
await setStellarAddressForUser(userId, validAddress);
// Should fail - placeholder
await setStellarAddressForUser(userId, "GTEST123...");
// Should fail - invalid format
await setStellarAddressForUser(userId, "INVALID");If issues arise after deployment:
-
Remove CHECK constraint:
ALTER TABLE users DROP CONSTRAINT chk_valid_stellar_address;
-
Revert application code:
git revert <commit-hash>
-
Monitor: Watch for any data integrity issues
- ✅
src/services/wallet/walletService.ts- Enhanced validation - ✅
prisma/schema.prisma- Added documentation - ✅
prisma/migrations/20260426000000_add_stellar_address_validation/migration.sql- New migration - ✅
prisma/validateStellarAddresses.ts- Validation script (new)
- No user can be created with invalid stellarAddress format
- Placeholder addresses are rejected at application layer
- Database CHECK constraint prevents invalid addresses
- Existing data validated before migration
- Validation on read catches any corrupted data
- Proper error logging for debugging
- Documentation added to schema
PostgreSQL doesn't have native access to Stellar SDK's StrKey.isValidEd25519PublicKey(). The CHECK constraint approximates this with:
- Length check (56 chars)
- Prefix check (starts with 'G')
- Character set check (base32: A-Z, 2-7)
- Placeholder pattern exclusion
Application-level validation provides the full StrKey checksum validation.
- Minimal: CHECK constraint is evaluated on INSERT/UPDATE only
- Index added for faster queries on non-null stellar addresses
- Validation on read only happens when asserting wallet address
- Consider adding a database trigger to log any validation failures
- Add monitoring alerts for invalid address attempts
- Create admin tool to audit stellar addresses periodically