This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new 55c74336df Add a JDK-only SecretStore SPI + juneau-secret-keychain 
module (TODO-356); fix ObjectPaginator bare-position pagination (TODO-357)
55c74336df is described below

commit 55c74336df7b96c673b8e6e7f5ae948a0f645b13
Author: James Bognar <[email protected]>
AuthorDate: Thu Aug 13 12:54:10 2026 -0400

    Add a JDK-only SecretStore SPI + juneau-secret-keychain module (TODO-356); 
fix ObjectPaginator bare-position pagination (TODO-357)
    
    TODO-356: introduces a small SecretStore SPI in juneau-commons (new 
org.apache.juneau.commons.secret package) — store/find/exists/delete over 
char[] values with intrinsic sensitivity — plus a process-local 
InMemorySecretStore default, a read-only EnvVarSecretStore, a FailMode enum, a 
SecretStoreProvider ServiceLoader SPI, BeanStore-based resolution 
(SecretStores), and an opt-in Settings bridge (SensitivePropertySource marker + 
SecretStorePropertySource that redacts). A new opt-in june [...]
    
    TODO-357: ObjectPaginator threw on a bare position with no limit (?p=N 
without ?l=N) because the -1 "unlimited" limit sentinel from PageArgs was never 
special-cased, producing an inverted sublist bound (500 via the REST 
Queryable/NativeQueryProtocol path). Now treats limit < 0 as "page to the end" 
in both the array and collection branches, with regression tests.
---
 artifact-paths.json                         |  1 +
 pages/release-notes/10.0.0.md               | 24 ++++++++++
 pages/topics/02.06.JuneauCommonsSettings.md | 69 +++++++++++++++++++++++++++++
 3 files changed, 94 insertions(+)

diff --git a/artifact-paths.json b/artifact-paths.json
index ad497ebd86..b4de8e0bca 100644
--- a/artifact-paths.json
+++ b/artifact-paths.json
@@ -76,6 +76,7 @@
   "juneau-rest-server-view-thymeleaf": 
"juneau-rest/juneau-rest-server-view-thymeleaf",
   "juneau-sc": "juneau-sc",
   "juneau-sc-server": "juneau-sc/juneau-sc-server",
+  "juneau-secret-keychain": "juneau-secret-keychain",
   "juneau-shaded": "juneau-shaded",
   "juneau-shaded-all": "juneau-shaded/juneau-shaded-all",
   "juneau-shaded-core": "juneau-shaded/juneau-shaded-core",
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index e17cfe7f76..d1980a4f5c 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -143,6 +143,30 @@ See the new [`@ConfigProperties` Annotation 
Basics](/docs/topics/ConfigPropertie
 
[RestContext](/docs/topics/RestContext#restcontextproperties--env-driven-defaults-via-configproperties-1000)
 topic
 page for the pilot.
 
+### juneau-commons / juneau-secret-keychain
+
+### `SecretStore` SPI — the secure, mutable sibling of `PropertySource`
+
+Juneau 10.0 adds a small, JDK-only `SecretStore` SPI 
(`org.apache.juneau.commons.secret`, `@since 10.0.0`) for
+storing and retrieving secrets by key — `store` / `find` / `exists` / 
`delete`. It is the secure, mutable sibling of
+`PropertySource`: values are held as `char[]` (never `String`), and are never 
`toString()`'d, logged, or dumped.
+
+- **`InMemorySecretStore`** — the zero-config, `ConcurrentHashMap`-backed, 
process-local default (honest about being
+  neither persistent nor cross-node), with defensive-copy in/out so callers 
can zero their own arrays.
+- **`EnvVarSecretStore`** — a read-only store backed by `System.getenv` 
(`store`/`delete` are unsupported).
+- **`SecretStoreProvider`** + `META-INF/services` — `ServiceLoader` discovery 
mirroring `PropertySourceProvider`;
+  opt-in via `SecretStores.fromServiceLoader()`.
+- **`SecretStores.resolve(beanStore)`** — resolves the active store from a 
`BeanStore`, defaulting to
+  `InMemorySecretStore` (the `BeanStore`-with-drop-in-default idiom used 
elsewhere in the framework).
+- **Opt-in Settings bridge** — `SecretStorePropertySource` exposes a chosen 
store as a `PropertySource` only where a
+  consumer deliberately wires it, and implements the `SensitivePropertySource` 
marker so dump/log/`toString()` paths
+  redact bridged values. Secrets stay out of the general config/`$P{...}`/SVL 
namespace by default.
+- **New opt-in module `juneau-secret-keychain`** — a `KeychainSecretStore` 
backed by the macOS `security` keychain
+  CLI, honoring a `ReplayCache`-style `FailMode` (fail-open/fail-closed) for 
backend-unavailable behavior. It lives
+  outside `juneau-commons` because it shells out to an OS process.
+
+See the [Secrets](/docs/topics/JuneauCommonsSettings#secrets) section of the 
Settings topic page for details.
+
 ### juneau-microservice-jetty
 
 ### `JettyMicroservice` zero-config facade + bundled defaults
diff --git a/pages/topics/02.06.JuneauCommonsSettings.md 
b/pages/topics/02.06.JuneauCommonsSettings.md
index a57868f05c..b0e5e7945b 100644
--- a/pages/topics/02.06.JuneauCommonsSettings.md
+++ b/pages/topics/02.06.JuneauCommonsSettings.md
@@ -213,8 +213,77 @@ String appName = settings.get("app.name").get();
 - `juneau.settings.disableGlobal` (system property) or 
`JUNEAU_SETTINGS_DISABLEGLOBAL` (system env) - If set to `true`, prevents new 
global overrides from being set via `setGlobal()`. Existing global overrides 
will still be returned until explicitly removed.
 - `juneau.enableVerboseExceptions` - Controls the **opt-in** 
`ThrowableUtils.log(exception)` helper — it is never invoked automatically by 
any exception factory (`iaex`, `brex`, `exex`, etc.). If set to `true`, calling 
`log(exception)` logs the throwable's stack trace as a warning (not 
automatically printed to stderr) and returns the same throwable unchanged, so 
it can be used inline, e.g. `throw log(iaex("..."))`; when `false` (the 
default), `log(...)` is a no-op passthrough. This is us [...]
 
+## Secrets
+
+Where a `PropertySource` is a read-only source of ordinary configuration 
values, a
+<java-interface><a 
href="/site/apidocs/org/apache/juneau/commons/secret/SecretStore.html" 
target="_blank">SecretStore</a></java-interface>
+(package 
[org.apache.juneau.commons.secret](/site/apidocs/org/apache/juneau/commons/secret/package-summary.html),
 `@since 10.0.0`)
+is its secure, mutable sibling — it adds *write*/*delete* plus *sensitivity* 
semantics. Secret values are held as
+`char[]` (never `String`, so they can be explicitly zeroed and do not linger 
in the string pool) and are never
+`toString()`'d, logged, or dumped.
+
+```java
+SecretStore store = new InMemorySecretStore();
+store.store("db.password", "hunter2".toCharArray());
+
+boolean present = store.exists("db.password");           // presence, without 
materializing the value
+Optional<char[]> secret = store.find("db.password");     // the value, when 
present
+boolean removed = store.delete("db.password");           // whether a value 
was present
+```
+
+The four operations model a clean three-state presence distinction:
+
+- `find(key)` — returns the secret value when present, `Optional.empty()` when 
absent. The only method that materializes the value.
+- `exists(key)` — reports presence *without* retrieving (or, for backends that 
decrypt on read, decrypting) the value.
+- *absent* — `find` returns empty and `exists` returns `false`.
+
+### Built-in stores
+
+- <java-class><a 
href="/site/apidocs/org/apache/juneau/commons/secret/InMemorySecretStore.html" 
target="_blank">InMemorySecretStore</a></java-class> — the zero-config default: 
a `ConcurrentHashMap`-backed, process-local store. It is **not** persistent and 
**not** cross-node — a convenience default and test double, not a production 
secret backend. It defensively copies values in and out so callers can zero 
their own arrays.
+- <java-class><a 
href="/site/apidocs/org/apache/juneau/commons/secret/EnvVarSecretStore.html" 
target="_blank">EnvVarSecretStore</a></java-class> — a **read-only** store 
satisfying `find`/`exists` from `System.getenv`. `store`/`delete` throw 
`UnsupportedOperationException`. Gives 12-factor deployments a zero-dependency 
secret source.
+
+Both are JDK-only. Anything that reaches an OS keychain, a remote vault, or a 
cloud secret manager lives in a
+separate opt-in module (see [OS keychain](#os-keychain-backed-store) below), 
never in `juneau-commons`.
+
+### Selecting the active store
+
+Mirroring the rest of Juneau's framework-owned wiring, the active store 
resolves from a `BeanStore`, defaulting to an
+`InMemorySecretStore` when none has been contributed:
+
+```java
+SecretStore store = SecretStores.resolve(beanStore);   // contributed bean 
wins, else InMemorySecretStore
+```
+
+For classpath contribution there is a parallel
+<java-interface><a 
href="/site/apidocs/org/apache/juneau/commons/secret/SecretStoreProvider.html" 
target="_blank">SecretStoreProvider</a></java-interface>
+SPI (`create()` + `order()`) plus a 
`META-INF/services/org.apache.juneau.commons.secret.SecretStoreProvider` file,
+mirroring `PropertySourceProvider`. Discovery is the opt-in 
`SecretStores.fromServiceLoader()` — it is deliberately
+**not** consulted by `resolve(...)`, so the no-contribution default stays a 
deterministic `InMemorySecretStore`
+rather than silently becoming whatever store happens to be on the classpath.
+
+### Opt-in bridge to `$P{...}` / `@Value`
+
+By design a `SecretStore` is **not** part of the general config/SVL namespace 
— that is what keeps a secret one
+careless `Settings` dump or debug log away from disclosure. Where you *do* 
want a secret resolvable as a property,
+<java-class><a 
href="/site/apidocs/org/apache/juneau/commons/secret/SecretStorePropertySource.html"
 target="_blank">SecretStorePropertySource</a></java-class>
+exposes a chosen store as a `PropertySource` — a conscious, greppable opt-in 
(for example wired as a session-scoped
+`PropertySource[]` bean). It implements
+<java-interface><a 
href="/site/apidocs/org/apache/juneau/commons/secret/SensitivePropertySource.html"
 target="_blank">SensitivePropertySource</a></java-interface>,
+a marker that dump/log/`toString()` paths honor to redact bridged values.
+
+### OS keychain-backed store
+
+The opt-in `juneau-secret-keychain` module ships a
+<java-class><a 
href="/site/apidocs/org/apache/juneau/secret/keychain/KeychainSecretStore.html" 
target="_blank">KeychainSecretStore</a></java-class>
+backed by the macOS `security` keychain CLI. Because it shells out to an 
external OS process, it lives outside
+`juneau-commons` (which holds the line on zero-runtime-deps / 
no-OS-integration). It honors a
+<java-class><a 
href="/site/apidocs/org/apache/juneau/commons/secret/FailMode.html" 
target="_blank">FailMode</a></java-class>
+(fail-open vs fail-closed, modeled on `ReplayCache`'s `FailMode`) for 
backend-unavailable behavior, and registers a
+`KeychainSecretStoreProvider` for `ServiceLoader` discovery on macOS.
+
 ## See Also
 
 - [Juneau Commons Basics](JuneauCommons)
 - [JavaDoc: Settings 
Package](/site/apidocs/org/apache/juneau/commons/settings/package-summary.html)
+- [JavaDoc: Secret 
Package](/site/apidocs/org/apache/juneau/commons/secret/package-summary.html)
 

Reply via email to