Thank you for considering contributing to NexPass! We welcome contributions from the community to help make NexPass the best privacy-focused password manager for Android.
We are committed to providing a welcoming and inclusive environment for all contributors, regardless of experience level, gender identity, sexual orientation, disability, personal appearance, race, ethnicity, age, religion, or nationality.
- Be respectful and constructive in discussions
- Welcome newcomers and help them get started
- Focus on what is best for the community and project
- Show empathy towards other community members
- Provide and accept constructive feedback gracefully
- Harassment, discrimination, or offensive comments
- Personal attacks or trolling
- Publishing others' private information
- Any conduct that would be inappropriate in a professional setting
Violations of the code of conduct can be reported to conduct@daguva.com. All complaints will be reviewed and investigated promptly and fairly.
Before creating a bug report:
- Check existing issues to avoid duplicates
- Use the latest version to ensure the bug hasn't been fixed
- Gather information about the bug
When reporting a bug, include:
- Clear title describing the issue
- Steps to reproduce the behavior
- Expected behavior vs. actual behavior
- Screenshots if applicable
- Device information:
- Android version
- Device manufacturer and model
- NexPass version
- Logs if available (Settings → About → Export Logs)
Use our Bug Report Template.
We love feature suggestions! Before creating a feature request:
- Check existing issues for similar requests
- Consider the scope - does it fit NexPass's goals?
- Think about privacy - does it maintain zero-knowledge principles?
When suggesting a feature, include:
- Clear title describing the feature
- Use case - what problem does it solve?
- Proposed solution - how should it work?
- Alternatives considered - what other solutions did you think about?
- Mockups or examples if applicable
Use our Feature Request Template.
- Discuss major changes in an issue first
- Check existing PRs to avoid duplicate work
- Ensure you can build the project successfully
# Clone your fork
git clone https://github.com/YOUR_USERNAME/nexpass.git
cd nexpass
# Add upstream remote
git remote add upstream https://github.com/codegax/nexpass.git
# Create a feature branch
git checkout -b feature/amazing-feature
# Build and test
./gradlew assembleDebug
./gradlew test- Fork the repository on GitHub
- Create a feature branch from
main- Feature:
feature/add-password-history - Bug fix:
fix/autofill-crash - Documentation:
docs/update-readme
- Feature:
- Make your changes following our coding standards
- Write/update tests for your changes
- Test thoroughly on a real device or emulator
- Commit your changes with clear messages
- Push to your fork and create a Pull Request
-
Title: Clear, descriptive, prefixed with type
feat: Add password history featurefix: Resolve autofill crash on Android 12docs: Update Nextcloud setup guiderefactor: Simplify crypto manager implementationtest: Add unit tests for folder repository
-
Description: Use the PR template
- What does this PR do?
- Why is this change needed?
- How was it tested?
- Screenshots (for UI changes)
- Related issues (closes #123)
-
Code Quality:
- Follows Kotlin coding conventions
- No linter warnings (
./gradlew lint) - Tests pass (
./gradlew test) - Code is well-documented
-
Small, Focused PRs: Easier to review and merge
- One feature/fix per PR
- Avoid mixing refactoring with features
- Split large features into multiple PRs
We follow the official Kotlin Coding Conventions:
// Classes: PascalCase
class PasswordRepository
// Functions and properties: camelCase
fun encryptPassword()
val userPassword: String
// Constants: UPPER_SNAKE_CASE
const val MAX_PASSWORD_LENGTH = 256
// Private properties: camelCase with leading underscore (optional)
private val _passwords = MutableStateFlow<List<Password>>(emptyList())
val passwords: StateFlow<List<Password>> = _passwords.asStateFlow()- Indentation: 4 spaces (no tabs)
- Line length: Max 120 characters
- Braces: K&R style (opening brace on same line)
// Good
fun validatePassword(password: String): Boolean {
if (password.isEmpty()) {
return false
}
return password.length >= 8
}
// Bad
fun validatePassword(password: String): Boolean
{
if (password.isEmpty())
{
return false
}
return password.length >= 8
}- Public APIs: Always document with KDoc
/**
* Encrypts a password using AES-256-GCM encryption.
*
* @param plaintext The password to encrypt in plaintext
* @param key The encryption key (must be 256 bits)
* @return Encrypted data containing ciphertext, IV, and tag
* @throws EncryptionException if encryption fails
*/
fun encryptPassword(plaintext: String, key: ByteArray): EncryptedData- Complex logic: Add inline comments explaining "why", not "what"
// Good: Explains reasoning
// Use PBKDF2 with 100k iterations to resist brute-force attacks
val derivedKey = deriveKey(masterPassword, salt, iterations = 100_000)
// Bad: Describes what code does (obvious)
// Call deriveKey function
val derivedKey = deriveKey(masterPassword, salt, iterations = 100_000)NexPass follows Clean Architecture + MVVM:
domain/ # Business logic (pure Kotlin, no Android deps)
├── model/ # Domain models
├── repository/ # Repository interfaces
└── usecase/ # Use cases
data/ # Data layer (Android-specific)
├── local/ # Room database, DAOs
├── network/ # API clients
└── repository/ # Repository implementations
ui/ # Presentation layer
├── screens/ # Composable screens
├── viewmodel/ # ViewModels (state management)
└── components/ # Reusable UI components
UI → ViewModel → UseCase/Repository → Data Source
- Domain layer has no dependencies on Android or data layer
- Data layer depends on domain (implements interfaces)
- UI layer depends on domain (ViewModels use use cases/repositories)
// Domain: Repository interface (domain/repository/)
interface PasswordRepository {
suspend fun getAllPasswords(): Result<List<PasswordEntry>>
suspend fun createPassword(password: PasswordEntry): Result<Unit>
}
// Data: Repository implementation (data/repository/)
class PasswordRepositoryImpl(
private val passwordDao: PasswordDao,
private val cryptoManager: CryptoManager
) : PasswordRepository {
override suspend fun getAllPasswords(): Result<List<PasswordEntry>> {
// Implementation
}
}
// UI: ViewModel (ui/viewmodel/)
class VaultListViewModel(
private val passwordRepository: PasswordRepository
) : ViewModel() {
// State and logic
}- All repository methods
- All use cases
- All ViewModels (state transitions)
- Crypto operations (critical!)
- Utility functions
- Security layer: 90%+ (critical for safety)
- Overall: 70%+ minimum
class PasswordRepositoryTest {
private lateinit var repository: PasswordRepositoryImpl
private lateinit var mockDao: PasswordDao
@Before
fun setup() {
mockDao = mockk()
repository = PasswordRepositoryImpl(mockDao, mockCrypto)
}
@Test
fun `getAllPasswords returns decrypted passwords`() = runTest {
// Given
val encryptedPassword = createMockEncryptedPassword()
whenever(mockDao.getAllPasswords()).thenReturn(listOf(encryptedPassword))
// When
val result = repository.getAllPasswords()
// Then
assertTrue(result.isSuccess)
assertEquals("decrypted", result.getOrNull()?.first()?.password)
}
}When contributing code that handles sensitive data:
- Never log sensitive information
// Bad
Log.d("Auth", "Master password: $masterPassword")
// Good
Log.d("Auth", "Master password validation completed")- Wipe sensitive data from memory
// Good
val passwordBytes = password.toByteArray()
try {
// Use passwordBytes
} finally {
MemoryUtils.wipeByteArray(passwordBytes)
}- Use secure random for cryptographic operations
// Good
val random = SecureRandom()
// Bad
val random = Random()- Validate all inputs
fun setMasterPassword(password: String) {
require(password.isNotBlank()) { "Password cannot be blank" }
require(password.length >= 8) { "Password must be at least 8 characters" }
// Proceed with secure operations
}<type>(<scope>): <subject>
<body>
<footer>
- feat: New feature
- fix: Bug fix
- docs: Documentation changes
- style: Formatting (no code change)
- refactor: Code refactoring
- test: Adding tests
- chore: Build/tooling changes
feat(autofill): Add fuzzy matching for subdomain detection
Implements fuzzy matching algorithm that matches login.example.com
with example.com credentials. Uses Levenshtein distance with 80%
similarity threshold.
Closes #42
fix(crypto): Resolve memory leak in key derivation
ByteArray instances were not being properly wiped after use, causing
sensitive data to remain in memory. Now uses try-finally blocks with
explicit wiping.
Fixes #87
- Functionality: Does it work as expected?
- Code quality: Is it clean, readable, maintainable?
- Tests: Are there adequate tests?
- Security: Does it maintain privacy/security standards?
- Performance: Is it efficient?
- Documentation: Is it well-documented?
- Initial review: Within 7 days
- Follow-up reviews: Within 3 days
- Merge: After approval from at least one maintainer
- Respond to all review comments
- Mark conversations as resolved when addressed
- Push new commits (don't force-push during review)
- Request re-review when ready
We follow Semantic Versioning:
- Major (1.0.0): Breaking changes
- Minor (0.1.0): New features (backward compatible)
- Patch (0.0.1): Bug fixes (backward compatible)
Releases are managed by maintainers through GitHub Actions.
- Questions: Open a Discussion
- Email: dev@daguva.com
Contributors are recognized in:
- README acknowledgments
- Release notes
- GitHub contributors page
Thank you for helping make NexPass better!
License Note: By contributing to NexPass, you agree that your contributions will be licensed under the GNU General Public License v3.0 (GPLv3).