Development#1
Merged
Merged
Conversation
… catalog - Move publishing coordinates to root gradle.properties (GROUP, VERSION_NAME) so CI/tag-driven releases can override without editing build scripts. - Register coroutines, androidx.security-crypto, robolectric libraries. - Register mavenPublish, dokka and binary-compatibility-validator plugins in libs.versions.toml (single source of truth for plugin versions).
- Use catalog plugin aliases for mavenPublish, dokka and binary-compat-validator
(no hard-coded versions in build scripts).
- Read group/version from root gradle.properties via Provider API.
- Enable Kotlin explicitApi = Strict and add -Xexpect-actual-classes /
-opt-in compiler args to keep the public surface deliberate.
- Pin Android JVM target to 17.
- Rename iOS framework to PascalCase SecureVaultKit and mark it static so
consumer apps link a single binary.
- Move dependencies to typed source-set DSL; drop redundant kotlin-stdlib
(KGP adds it implicitly). Expose coroutines as api on commonMain since
the public API uses suspend / Flow.
- Configure vanniktech publishing for KMP with JavadocJar.Dokka + sourcesJar
and call signAllPublications(); drop the bespoke signing {} block.
- Fix invalid groupId (io.github.alims-repo -> io.github.alimsrepo),
fill the previously empty POM description, add issueManagement and
distribution=repo on the license.
- Remove Platform.kt stub triplet; the real public API lands next.
Replace the placeholder expect fun platform() with the real library contract: - SecureVault interface: suspend put/get/remove/contains/clear/keys with documented @throws and a coroutine-first shape. - VaultException sealed hierarchy: InvalidKey, CryptoFailure, Tampered, StorageUnavailable. Lets common code catch all storage errors uniformly without depending on java.* or platform.Security.*. - VaultConfig immutable data class + Accessibility enum modelling Keychain kSecAttrAccessible* semantics, with a namespace regex validated at construction time (fail-fast). - SecureVaultFactory expect class - the single platform entry point that hides Context / Service constructor differences from common code. - Internal requireValidKey() helper so both backends enforce the same key contract. Every public symbol is documented with KDoc and explicit visibility (the module compiles with explicitApi = Strict). The Android and iOS actuals follow in the next commits.
- SecureVaultFactory.android: actual class with Context constructor; stores
applicationContext only to avoid leaking Activity/Fragment contexts.
- AndroidSecureVault: internal implementation using Jetpack Security's
EncryptedSharedPreferences with AES-256 SIV keys + AES-256 GCM values,
wrapped by an Android Keystore master key (AES256_GCM scheme).
- Lazy, mutex-guarded prefs initialisation so constructors stay non-blocking
and concurrent first-use callers cannot race the file creation.
- Per-namespace file via the secure_vault__<namespace> prefix, isolating
our data from any other SharedPreferences the host app uses.
- All blocking I/O dispatched on Dispatchers.IO; platform exceptions
(AEADBadTagException, GeneralSecurityException, IOException) are mapped
to VaultException.{Tampered, CryptoFailure, StorageUnavailable} via a
small inline runCatchingStorage helper so java.* never leaks across the
library boundary.
- SecureVaultFactory.ios: zero-arg actual class; the Keychain is process-wide
so no platform handle is needed at construction time.
- IosSecureVault: internal implementation using SecItem{Add,Update,Copy,Delete}
with kSecClassGenericPassword and per-namespace kSecAttrService isolation.
- Accessibility maps onto kSecAttrAccessible{AfterFirstUnlock,WhenUnlocked}
ThisDeviceOnly so secrets never sync via iCloud Keychain or migrate to a
different device.
- Queries are constructed as NSMutableDictionary then toll-free-bridged to
CFDictionaryRef; kSec* string constants are bridged via NSCopying for use
as dictionary keys. All blocking calls dispatched on Dispatchers.Default.
- OSStatus codes are translated to VaultException subtypes: errSecDecode ->
Tampered, errSecDuplicateItem -> CryptoFailure, anything else ->
StorageUnavailable. errSecItemNotFound is treated as a normal absent
result rather than an error.
- put() performs SecItemUpdate first and falls back to SecItemAdd on
errSecItemNotFound — Keychain has no native upsert.
Adds the IosSecureVault class that was missed from the previous commit.
Uses SecItem{Add,Update,Copy,Delete} on kSecClassGenericPassword items,
scoped per VaultConfig.namespace via kSecAttrService.
- Accessibility maps to kSecAttrAccessible{AfterFirstUnlock,WhenUnlocked}
ThisDeviceOnly so secrets never sync via iCloud or migrate across devices.
- Queries are NSMutableDictionary instances toll-free bridged to
CFDictionaryRef; kSec* constants are bridged via NSCopying for use as
dictionary keys.
- All blocking calls dispatched on Dispatchers.Default.
- OSStatus codes translate to VaultException subtypes:
errSecDecode -> Tampered
errSecDuplicateItem -> CryptoFailure
other failures -> StorageUnavailable
errSecItemNotFound is a normal absent-result, not an error.
- put() performs SecItemUpdate first and falls back to SecItemAdd on
errSecItemNotFound, since Keychain has no native upsert.
- FakeSecureVault: internal, mutex-guarded in-memory implementation kept in commonTest so it never enters the published API surface. - SecureVaultContractTest: abstract behavioural contract every backend must satisfy (round-trip, overwrite, contains/remove idempotency, keys snapshot, clear, blank-key rejection). Subclassing keeps the assertions in one place; the iOS Keychain backend will plug into the same harness once simulator tests are wired up. - FakeSecureVaultTest: instantiates the fake against the contract. - VaultConfigTest: regression tests for the namespace regex and default Accessibility. Android Robolectric tests are intentionally deferred until AGP's com.android.kotlin.multiplatform.library plugin exposes a stable host-test surface; tracked for v0.2.
- Replace the IDE-template README with a library-focused one: badges, 60-second install snippet, supported targets table, Android + iOS usage, VaultException handling guide, threading notes, build/publish commands. - Add LICENSE (Apache-2.0) at the repo root so vanniktech-maven-publish and Maven Central pick it up alongside the POM <licenses> block. - Add CHANGELOG.md following Keep a Changelog 1.1.0 + SemVer; seed it with the 0.1.0 entry, including explicit "known limitations" so consumers know what is and isn't in scope. - Add SECURITY.md with a responsible-disclosure policy, supported-version matrix, and an honest threat-model section that names the OS guarantees we inherit and the things SecureVault explicitly does *not* defend against. - Add an .editorconfig pinning Kotlin formatting (4-space indent, LF, trailing newline, 120-col max, trailing commas allowed) so contributors and CI see the same style.
Setting `group`/`version` eagerly at the top of the build script finalises
the maven-publish plugin's `groupId`/`version` properties before the
`mavenPublishing { coordinates(...) }` block can configure them, causing:
The value for extension 'mavenPublishing' property 'groupId$plugin'
is final and cannot be changed any further.
Vanniktech's coordinates() already propagates the values to
`project.group` / `project.version`, so the top-level assignment is
redundant. Removing it restores configuration ordering and the build
script now scripts correctly.
Even after removing the top-level group/version assignment, the
mavenPublishing { coordinates(...) } call still fails on AGP 9.x:
property 'groupId$plugin' is final and cannot be changed any further
Root cause: AGP's com.android.kotlin.multiplatform.library plugin
finalises the maven-publish groupId Property as a side-effect of
registering the AAR publication, before our script reaches
coordinates(...).
Fix: drop coordinates(...) entirely. vanniktech-maven-publish reads
GROUP / POM_ARTIFACT_ID / VERSION_NAME from gradle.properties and
applies them via plugin convention — i.e. *before* any other plugin
can finalise the Property. Adds POM_ARTIFACT_ID=secure-vault to root
gradle.properties and replaces the in-script coordinates(...) block
with a comment explaining why.
Single-file, dependency-free landing page at docs/index.html, ready to be served from the repo's GitHub Pages "/docs on main" source. - Dark, modern theme with gradient hero, sticky blurred navbar, custom SVG brand mark; Inter + JetBrains Mono via Google Fonts. - Sections: hero with one-line pitch and primary/secondary CTAs, tabbed install card (Kotlin DSL / version catalog / Groovy DSL), why-secure-vault feature grid (OS-native crypto, coroutine-first, sealed errors, namespaces, tiny API, ABI-stable), Android+iOS usage cards styled as IDE tabs, API table, supported-targets matrix, footer with Maven Central / changelog / security links. - Vanilla JS only: tab switching + copy-to-clipboard for the install snippet. No build step, no bundler, no framework — drop-in for Pages. - Inline minimal Kotlin/Swift syntax highlighting via .tk-* token classes so the page stays one file with zero runtime deps.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new :secure-vault Kotlin Multiplatform library module that provides a coroutine-first SecureVault API backed by native secure storage on Android (EncryptedSharedPreferences) and iOS (Keychain), along with publishing and documentation assets.
Changes:
- Added the
secure-vaultKMP module with common API (SecureVault,VaultConfig,VaultException) and Android/iOS implementations. - Added Maven Central publishing configuration (Gradle + GitHub Actions) and ABI validation tooling.
- Added project documentation (README, SECURITY policy, changelog, GitHub Pages docs site).
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Includes the new :secure-vault module in the build. |
| SECURITY.md | Adds a responsible disclosure policy and threat model notes. |
| secure-vault/src/commonMain/kotlin/io/github/alimsrepo/secure/vault/SecureVault.kt | Defines the cross-platform secure storage interface. |
| secure-vault/src/commonMain/kotlin/io/github/alimsrepo/secure/vault/SecureVaultFactory.kt | Declares the expect factory entry point for platform implementations. |
| secure-vault/src/commonMain/kotlin/io/github/alimsrepo/secure/vault/VaultConfig.kt | Adds namespace/accessibility configuration for vault instances. |
| secure-vault/src/commonMain/kotlin/io/github/alimsrepo/secure/vault/VaultException.kt | Introduces a sealed exception hierarchy for cross-platform error handling. |
| secure-vault/src/commonMain/kotlin/io/github/alimsrepo/secure/vault/internal/Validation.kt | Centralizes key validation shared by all backends. |
| secure-vault/src/androidMain/kotlin/io/github/alimsrepo/secure/vault/SecureVaultFactory.android.kt | Android actual factory implementation (Context-backed). |
| secure-vault/src/androidMain/kotlin/io/github/alimsrepo/secure/vault/AndroidSecureVault.kt | Android EncryptedSharedPreferences-backed vault implementation. |
| secure-vault/src/androidMain/AndroidManifest.xml | Adds the Android manifest for the library module. |
| secure-vault/src/iosMain/kotlin/io/github/alimsrepo/secure/vault/SecureVaultFactory.ios.kt | iOS actual factory implementation (no handle required). |
| secure-vault/src/iosMain/kotlin/io/github/alimsrepo/secure/vault/IosSecureVault.kt | iOS Keychain-backed vault implementation and CF/NS bridging helpers. |
| secure-vault/src/commonTest/kotlin/io/github/alimsrepo/secure/vault/SecureVaultContractTest.kt | Adds shared behavioral contract tests for vault implementations. |
| secure-vault/src/commonTest/kotlin/io/github/alimsrepo/secure/vault/FakeSecureVault.kt | Adds an in-memory implementation for contract testing. |
| secure-vault/src/commonTest/kotlin/io/github/alimsrepo/secure/vault/FakeSecureVaultTest.kt | Runs the contract against the in-memory fake. |
| secure-vault/src/commonTest/kotlin/io/github/alimsrepo/secure/vault/VaultConfigTest.kt | Tests VaultConfig validation and defaults. |
| secure-vault/build.gradle.kts | Configures KMP targets, dependencies, and Maven Central publishing. |
| secure-vault/.gitignore | Ignores the module build output. |
| gradle/libs.versions.toml | Adds versions/libs/plugins for publishing, Dokka, BCV, coroutines, security-crypto. |
| gradle.properties | Adds publishing coordinates and adjusts Gradle caching settings. |
| .github/workflows/publish.yml | Adds a tag-triggered Maven Central publishing workflow. |
| README.md | Replaces template content with library usage/installation docs. |
| docs/index.html | Adds a static docs/landing page for GitHub Pages. |
| CHANGELOG.md | Adds initial changelog entries for v0.1.0. |
| LICENSE | Adds Apache-2.0 license file. |
| build.gradle.kts | Adds the Android lint plugin alias at the root. |
| .editorconfig | Adds repository-wide formatting/editor conventions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+18
to
+20
| GROUP=io.github.alims-repo | ||
| POM_ARTIFACT_ID=secure-vault | ||
| VERSION_NAME=0.1.0 |
Comment on lines
+7
to
+8
| org.gradle.configuration-cache=false | ||
| org.gradle.caching=false |
Comment on lines
+33
to
+35
| run: | | ||
| export ORG_GRADLE_PROJECT_signingInMemoryKey=$(echo "$SIGNING_KEY_B64" | base64 -d) | ||
| ./gradlew :secure-vault:publishAllPublicationsToMavenCentralRepository --no-configuration-cache |
Comment on lines
+179
to
+183
| throw when (this) { | ||
| errSecDecode -> VaultException.Tampered(RuntimeException(msg)) | ||
| errSecDuplicateItem -> VaultException.CryptoFailure(RuntimeException(msg)) | ||
| else -> VaultException.StorageUnavailable(RuntimeException(msg)) | ||
| } |
Comment on lines
+39
to
+47
| public expect class SecureVaultFactory { | ||
|
|
||
| /** | ||
| * Builds a [SecureVault] for the given [config]. Multiple calls with the | ||
| * same [VaultConfig.namespace] return independent instances that address | ||
| * the same underlying storage. | ||
| */ | ||
| public fun create(config: VaultConfig): SecureVault | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.