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)