Release Java SDK v18.0.0 - #1127
Conversation
Removes a section-header decorator, several em dashes, four "This method" docstring restatements, and inline ticket references from comments across core. Ticket refs belong in commit messages, not code; the substantive explanation in each is kept. No behavior change.
…#1109) KSM-1248: reject server-supplied key_id values outside the keeperPublicKeys map (keys 7-18) before writing them to storage. A hostile server cannot poison the stored key_id and break subsequent calls. KSM-1262: saveCachedValue and LocalConfigStorage.saveToFile now write to a temp file with 0600 permissions set before any data is written, then replace the target via Files.move(ATOMIC_MOVE). This eliminates the window where the file was world-readable between create and chmod. Also removes ticket refs from comments and rewrites affected comments in STE-flavored prose.
When the config or cache directory is not writable, the temp-file creation fails with an AccessDeniedException that names the temp path. Wrap createTempFile in both saveToFile and saveCachedValue to rethrow as SecretsManagerException naming the config/cache path and its parent, matching the guidance already in the changelog. Guard testConfigFileAtomicWriteIsOwnerOnly with org.junit.Assume so it skips rather than fails on Windows or non-POSIX file systems.
* fix(java): use SecureRandom for generatePassword shuffle (KSM-1203) The final shuffle in generatePassword used Kotlin's Random.Default (a non-cryptographic PRNG), making the character arrangement predictable from PRNG state even though character selection was already secure. Switch to Collections.shuffle with SecureRandom.getInstanceStrong() so the entire password generation path uses a CSPRNG. * docs(java): add KSM-1203 changelog entry
* feat(java): expose isEditable on KeeperRecord (KSM-1176) The server returns isEditable in the record envelope and the private DTO already parsed it, but KeeperRecord had no corresponding field. Add isEditable: Boolean = false to KeeperRecord (default preserves existing callers) and forward record.isEditable in decryptRecord(). * docs(java): add KSM-1176 changelog entry
…1116) * fix(java): set connect/read timeouts on all HttpsURLConnection calls (KSM-1207) HttpsURLConnection defaults to timeout 0 (infinite), allowing a stalled or hostile server to block the caller indefinitely. Add connectTimeoutMillis (default 5s) and readTimeoutMillis (default 30s) to SecretsManagerOptions and thread them through to postFunction. Apply module-level constants at the downloadFile and uploadFile private helpers whose callers don't carry options. * docs(java): add KSM-1207 changelog entry * fix(java): use module constants for postFunction timeout defaults
…ase (#1117) * fix(java): keep the four-argument postFunction callable from Java (KSM-1207) Kotlin default arguments do not exist in bytecode, so adding connectTimeoutMillis and readTimeoutMillis replaced the published static postFunction(String, TransmissionKey, EncryptedPayload, boolean) rather than extending it. Java callers stop compiling, and callers already compiled against 17.3.0 fail at runtime with NoSuchMethodError. @jvmoverloads restores the four- and five-argument forms alongside the new one. Affects the ServiceNow credential resolver and the hello-secret-custom-caching example, both of which call the four-argument form. * fix(java): keep KeeperRecord's constructor Java-compatible (KSM-1176) isEditable was inserted between revision and files. KeeperRecord carries no @jvmoverloads, so Java sees only the full-arity constructor and the parameter count went from nine to ten, breaking every Java caller. Moving the field to the end and adding @jvmoverloads restores the original nine-argument constructor exactly, with no change to the Kotlin API or to JSON decoding (KeeperRecord is not @serializable). The internal construction site now uses named arguments so a future field cannot silently shift positions again. * fix(java): apply the caller's timeouts to the file upload transport (KSM-1207) uploadFile(options, ownerRecord, file) already holds the options and passes them to postQuery for the add_file request, but the private helper that performs the actual upload hardcoded the module defaults. A caller raising readTimeoutMillis for a slow link saw it honoured for the metadata request and silently ignored for the upload in the same call. downloadFile keeps the module constants: its public entry points take a KeeperFile and no options, so there is nothing to thread through. * test(java): verify readTimeout actually bounds a stalled server (KSM-1207) The previous test asserted that SecretsManagerOptions echoes its own constructor arguments, which holds for any Kotlin data class and passes unchanged if both connection.readTimeout assignments are deleted. TimeoutTest points postFunction at a ServerSocket that accepts the connection and then goes silent, so the client blocks reading the ServerHello. With the timeout applied the call fails in about one second; without it the read never returns, so the call runs under a watchdog on a daemon thread that fails the test at twenty seconds instead of hanging the Gradle test JVM. The defaults are asserted separately, since 5000/30000 is the documented contract. Connect timeout has no behavioural test: it needs a blackholed address, which is not reproducible across CI environments. * chore(java): bump core version to 17.4.0 The release branch and the changelog both say 17.4.0, but build.gradle.kts and KEEPER_CLIENT_VERSION still said 17.3.0. publish.maven.java.core.yml takes no version input and reads the coordinate straight from build.gradle.kts, so the release would have tried to republish 17.3.0 to Maven Central and reported mj17.3.0 in the client version header. * test(java): anchor the accepted socket so GC cannot close it mid-handshake The socket returned by accept() was discarded, so it turned unreachable as soon as the acceptor thread exited. A GC cycle inside the one-second window then closed the server side underneath the TLS handshake and the client failed on the wrong exception, making the assertion look like the timeout code was broken. Confirmed under forced GC, and not marginal: with the socket discarded the call failed in 30-90ms with SSLException on JDK 8 and SocketException on JDK 21, never once with SocketTimeoutException. Anchored, it times out at ~1.04s on both. The same negative control run through Gradle fails in 0.15s on the wrong exception type. Held in an AtomicReference rather than a captured var: the acceptor thread writes it and the test thread reads it to close, and a captured var compiles to a non-volatile Ref.ObjectRef, so the reader could see a stale null and silently skip the close. Joining the acceptor before closing makes the handoff ordered, and the close in a finally block stops the test leaking the socket either way. Raised by Stas Schaller in review of #1117.
…llow-ups (#1120) * docs(java): document the 17.4.0 breaking changes in the changelog The 17.4.0 section listed only features and fixes. Enumerating every class in the compiled jar and diffing javap output against master shows exactly three public members change relative to 17.3.0, plus the two synthetic constructors Kotlin emits for default arguments: deleteFolder() return type changed KeeperRecord.copy() arity changed SecretsManagerOptions.copy() arity changed deleteFolder() was already called out in the release PR description but never reached the changelog, which is what users actually read on upgrade. The second entry covers a case easy to state too narrowly. Adding a constructor parameter changes copy(), but it also changes the synthetic $default constructor, so Kotlin code that merely builds a KeeperRecord while omitting a defaulted argument breaks on a jar swap too. Verified: a caller compiled against 17.3.0 and run against 17.4.0 throws NoSuchMethodError: KeeperRecord.<init>(..., int, DefaultConstructorMarker) Recompiling resolves it. Java call sites are unaffected either way, since both types carry @jvmoverloads. Matches the Breaking Changes block the 17.3.0 section already uses. * fix(java): keep the underlying IOException when a config write fails (KSM-1262) Both temp-file write paths caught IOException and threw a SecretsManagerException that discarded the original exception, so the stack trace and errno were lost. The message also asserted a cause it could not know: createTempFile fails on a full or read-only volume, a missing parent directory, or an fd limit, none of which are a permission problem, and the operator was told the directory was not writable. SecretsManagerException gains an optional cause. @jvmoverloads keeps the single-argument constructor, so Java callers and the existing subclasses that call super(message) are unaffected; javap confirms no member was removed from the class. * docs(java): document KeeperRecord.isEditable semantics (KSM-1176) The field carries a backend permission decision, and the only description of it lived in the changelog. Records the three things a caller needs: the meaning of each value, that the SDK does not enforce it before an update, and that the `false` default is reachable only through direct construction because the response envelope requires the field. * test(java): assert the record decrypts before reading isEditable (KSM-1176) The test indexed records[0] directly. fetchAndDecryptSecrets swallows per-record failures to stderr, so a decrypt regression would have surfaced as IndexOutOfBoundsException rather than a readable assertion; both neighbouring tests already guard on the record count first. Folds the two cases into a loop over true/false. Verified the assertion still discriminates: hardcoding isEditable = true in decryptRecord fails the test, and reverting passes.
Add release/sdk/java/core/** to pull_request and push triggers in test.java.yml so CI fires on PRs targeting or pushing to the release branch.
…1118) * fix(java): KSM-1270 guard getSharedFolderKey against parent-cycle infinite loop Replace unbounded while(true) with a HashSet<String> visited guard. Add testGetFoldersSkipsFolderParentCycle to verify the call completes and returns an empty list instead of hanging. * KSM-1270 Address Mateo's review: test timeout, distinct cycle error, README fix - Add @test(timeout = 5_000) so a regression hangs for at most 5 s rather than blocking the entire Gradle test task indefinitely - Throw SecretsManagerException with a cycle-specific message instead of returning null, so operators can distinguish a parent cycle from other folder-key lookup failures in stderr output - Fold the two-line KSM-1270 README entry into one line to match the surrounding bullets
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Summary
Release branch for v18.0.0 of the Java SDK. Bundles bug fixes for folder key handling, getFolders crash safety, per-item delete error surfacing, secure config writes, and several API additions.
Changes
Bug Fixes
getFolders()failed with "App key is missing" when called as the first method on a freshly bound application. It now processesencryptedAppKeyfrom the server binding response the same waygetSecrets()does.getSharedFolderKeylooped indefinitely when server folder data contained a parent cycle. The function now tracks visited UIDs and exits on re-visit;getFoldersskips the affected folders.getFolders()threw when any folder in the response had a corrupted or missing key. The SDK now skips undecryptable folders and returns the rest normally.deleteFolder()decoded the response into the records-only type, silently dropping all folder data. It now returns a typedSecretsManagerDeleteFolderResponsewith per-folder status.generatePasswordusedRandom.Default(non-cryptographic) for the final character shuffle. The shuffle now usesSecureRandom.SecretsManagerExceptionand leaves storage unchanged.New Features
connectTimeoutMillis(default 5 000 ms) andreadTimeoutMillis(default 30 000 ms) toSecretsManagerOptions. A stalled server now causes aSocketTimeoutExceptionrather than an indefinite hang.KeeperRecordnow exposesisEditable: Booleanfrom the server response envelope.Breaking Changes
deleteFolder()returnsSecretsManagerDeleteFolderResponseinstead ofSecretsManagerDeleteResponse. Callers that read.recordson the old return type must switch to.folders.KeeperRecordandSecretsManagerOptionseach gained a constructor parameter. Kotlincopy()and synthetic constructors changed arity — rebuild dependents against v18.0.0 rather than replacing the jar in place.Security Impact
KSM-1045 touches the initial key derivation path:
fetchAndDecryptFoldersnow decryptsencryptedAppKeyusing the client key and stores the result as the app master key, matching the behavior already infetchAndDecryptSecrets. No new cryptographic operations introduced.KSM-1203 corrects the entropy source for password generation. The final character shuffle previously used
Random.Default(a non-cryptographic PRNG); it now usesSecureRandom. The rest of the generation path was already cryptographically secure.KSM-1262 closes a TOCTOU window on POSIX systems where a local user could read a config or cache file during a write.
Related Issues