diff --git a/.env.example b/.env.example
index fd46c0e..e7fda17 100644
--- a/.env.example
+++ b/.env.example
@@ -14,7 +14,8 @@
LDAP_BASE_DN=dc=example,dc=com
# DNS domain (dc=foo,dc=bar -> foo.bar). Leave blank to derive from LDAP_BASE_DN.
LDAP_DOMAIN=
-LDAP_ADMIN_PASS=change-me-ldap-admin-password
+# LDAP admin password. MUST be changed. Leave blank and setup.sh will generate one.
+LDAP_ADMIN_PASS=CHANGE-ME
ORG_NAME="My Org"
# ── Public hostnames (REQUIRED) ───────────────────────────────────────────────
@@ -30,13 +31,15 @@ PROXY_HOST=proxy.example.com
# app_sso_oauth_admin, and logs in as them to register the proxy OAuth client.
# Re-running setup.sh resets this password to BOOTSTRAP_ADMIN_PASS.
BOOTSTRAP_ADMIN_UID=admin
-BOOTSTRAP_ADMIN_PASS=change-me-admin-password
+# First admin password. MUST be changed. Leave blank and setup.sh will generate one.
+BOOTSTRAP_ADMIN_PASS=CHANGE-ME
BOOTSTRAP_ADMIN_EMAIL=admin@example.com
# ── Proxy LDAP service account (created by the bootstrap) ────────────────────
# The proxy binds to LDAP as cn=ldapclient,ou=people, with this password.
# Re-running setup.sh resets it to LDAP_SERVICE_PASS.
-LDAP_SERVICE_PASS=change-me-ldap-service-password
+# LDAP service-account password. MUST be changed. Leave blank and setup.sh will generate one.
+LDAP_SERVICE_PASS=CHANGE-ME
# ── OAuth JWT secret (REQUIRED — persist it) ────────────────────────────────
# Signs the SSO's access/refresh tokens. Generate with: openssl rand -hex 32
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 890e435..61fdb22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,49 @@ for what changed inside the apps it composes.
## [Unreleased]
+## [1.1.18] - 2026-07-18
+
+### Bumped
+- proxy -> [v1.1.16](https://github.com/theta42/proxy/releases/tag/v1.1.16)
+- sso-manager-node -> [v1.1.16](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.16)
+
+proxy:
+
+### Changed
+- Public-release packaging: removed `"private": true` from `nodejs/package.json`, corrected the repository URL to `https://github.com/theta42/proxy.git`, and fixed the MIT `LICENSE` copyright line.
+- Genericized committed config defaults in `conf/base.js` and `conf/development.js` (`example.com` / `localhost` instead of theta42 infrastructure).
+- The bootstrap `proxyadmin2` account now gets a random, one-time password when `auth.localAdminPass` is unset, instead of the well-known default.
+
+### Security
+- Sanitized rendered docs HTML with `xss` in `routes/docs.js`.
+- The Unix socket JSON-RPC socket is now created with mode `660` instead of world-writable `777`.
+
+### Fixed
+- The global error handler no longer leaks `err.keys`, stack traces, or internal details in JSON responses.
+- `DEPLOYMENT.md` and `docs/docker.md` now correctly describe the `CONF_SECRETS` env-var mechanism.
+
+sso-manager-node:
+
+### Security
+- Hardened LDAP filter and DN construction against injection in `models/group_ldap.js` and `models/user_ldap.js`.
+- Replaced `Math.random()`-based token/UUID/OTP generation with `crypto.randomUUID()` / `crypto.randomInt()` in `models/token.js`, `models/oauth_code.js`, and `models/oauth_client.js`.
+- Refused startup when `oauth.jwtSecret` is missing or placeholder.
+- Sanitized rendered docs/Terms-of-Service HTML with `xss` to block malicious markdown output.
+- Removed full-object `console.log` of new-user data and reduced login error logging to `name`/`message` only.
+
+### Changed
+- Public-release packaging: removed `"private": true` from `nodejs/package.json` and bumped version to `1.1.16`.
+
+### Fixed
+- `models/email.js`: fixed from-address template rendering bug.
+
+### theta-env own changes
+- `CHANGELOG.md` now embeds the full app-level release notes for each submodule bump, not just links.
+- `.env.example` no longer ships realistic-looking default passwords; values are clearly placeholders.
+- `config.example/*.js.example` comments now describe the actual `CONF_SECRETS` env-var loading mechanism.
+- `setup.sh` summary no longer prints generated passwords to stdout; it points to `./config/*.js`.
+- `bootstrap/bootstrap.js` fails hard instead of falling back to weak default passwords when config is missing.
+
## [1.1.17] - 2026-07-18
### Bumped
@@ -214,7 +257,7 @@ First tagged release. Establishes the `vX.Y.Z` tag convention going forward.
- proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0)
- sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0)
-[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.17...HEAD
+[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.18...HEAD
[1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17
[1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16
[1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15
diff --git a/bootstrap/bootstrap.js b/bootstrap/bootstrap.js
index a9a15b5..3e5a873 100644
--- a/bootstrap/bootstrap.js
+++ b/bootstrap/bootstrap.js
@@ -44,8 +44,15 @@ const fs = require('fs');
const sso = require('/config/sso-secrets.js');
const proxy = require('/config/proxy-secrets.js');
-const BASE_DN = (sso.stack && sso.stack.ldapBaseDn) || 'dc=example,dc=com';
-const ADMIN_PASS = (sso.ldap && sso.ldap.bindPassword) || 'admin';
+function requireConf(value, name) {
+ if (value === undefined || value === null || value === '' || value === 'CHANGE-ME') {
+ throw new Error(`${name} is not configured in /config/sso-secrets.js`);
+ }
+ return value;
+}
+
+const BASE_DN = requireConf((sso.stack && sso.stack.ldapBaseDn), 'stack.ldapBaseDn');
+const ADMIN_PASS = requireConf((sso.ldap && sso.ldap.bindPassword), 'ldap.bindPassword');
const BIND_DN = `cn=admin,${BASE_DN}`;
const LDAP_URL = 'ldap://localhost:389';
@@ -53,9 +60,9 @@ const ADMIN_UID = (sso.bootstrap && sso.bootstrap.adminUid) || 'admin';
// The first admin *user's* password (cn=,ou=people,). Distinct from
// ADMIN_PASS above, which is the LDAP *root* (cn=admin,) bind password —
// two different accounts, two different secrets.
-const ADMIN_USER_PASS = (sso.bootstrap && sso.bootstrap.adminPass) || 'admin';
+const ADMIN_USER_PASS = requireConf((sso.bootstrap && sso.bootstrap.adminPass), 'bootstrap.adminPass');
const ADMIN_EMAIL = (sso.bootstrap && sso.bootstrap.adminEmail) || '';
-const SVC_PASS = sso.serviceAccountPass || 'service';
+const SVC_PASS = requireConf(sso.serviceAccountPass, 'serviceAccountPass');
const SSO_HOST = (sso.stack && sso.stack.ssoHost) || 'sso.example.com';
const PROXY_HOST = (sso.stack && sso.stack.proxyHost) || 'proxy.example.com';
diff --git a/config.example/proxy-secrets.js.example b/config.example/proxy-secrets.js.example
index 7e4c92d..d53312a 100644
--- a/config.example/proxy-secrets.js.example
+++ b/config.example/proxy-secrets.js.example
@@ -5,8 +5,8 @@
// bootstrap writes the OAuth client clientId/clientSecret back into it; this
// file documents the shape for manual editing / reference.
//
-// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh
-// symlinks it to /app/conf/secrets.js). Never commit ./config/.
+// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh sets
+// CONF_SECRETS to point at it). Never commit ./config/.
module.exports = {
oidc: {
diff --git a/config.example/sso-secrets.js.example b/config.example/sso-secrets.js.example
index 060ae10..03645d0 100644
--- a/config.example/sso-secrets.js.example
+++ b/config.example/sso-secrets.js.example
@@ -4,8 +4,8 @@
// `./setup.sh` generates ./config/sso-secrets.js for you on first run; this file
// documents the shape for manual editing / reference.
//
-// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh symlinks
-// it to /app/conf/secrets.js). The app ignores the extra stack/bootstrap/
+// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh sets
+// CONF_SECRETS to point at it). The app ignores the extra stack/bootstrap/
// serviceAccountPass keys (read by the orchestrator). Back this up off-host —
// it holds all SSO secrets. Never commit ./config/.
diff --git a/proxy b/proxy
index c0e1aa6..289a958 160000
--- a/proxy
+++ b/proxy
@@ -1 +1 @@
-Subproject commit c0e1aa666e22d603534c996ee7c681b131ee4f89
+Subproject commit 289a9587d62facf67876efc85472f01576e1d6d2
diff --git a/setup.sh b/setup.sh
index cbdba57..bb7e58e 100755
--- a/setup.sh
+++ b/setup.sh
@@ -610,8 +610,6 @@ read_config_kv() {
LDAP_BASE_DN: (c.stack && c.stack.ldapBaseDn) || "",
ORG_NAME: c.name || "",
ADMIN_UID: (c.bootstrap && c.bootstrap.adminUid) || "",
- ADMIN_PASS: (c.bootstrap && c.bootstrap.adminPass) || "",
- PROXY_LOCAL_ADMIN_PASS: (p.auth && p.auth.localAdminPass) || "",
};
for (const k in o) console.log(k + "=" + (o[k] == null ? "" : o[k]));
' 2>/dev/null
@@ -621,8 +619,6 @@ cfgval() { echo "$CFG_OUT" | grep -m1 "^$1=" | cut -d= -f2-; }
SSO_HOST="$(cfgval SSO_HOST)"
PROXY_HOST="$(cfgval PROXY_HOST)"
ADMIN_UID="$(cfgval ADMIN_UID)"
-ADMIN_PASS="$(cfgval ADMIN_PASS)"
-PROXY_LOCAL_ADMIN_PASS="$(cfgval PROXY_LOCAL_ADMIN_PASS)"
info "Stack config:"
info " SSO host: https://${SSO_HOST}"
@@ -717,13 +713,13 @@ echo " first-run fallback: http://127.0.0.1:${SSO_PORT:-300
echo " Proxy mgmt UI: https://${PROXY_HOST}"
echo " first-run fallback: http://127.0.0.1:${MGMT_PORT:-3000}"
echo
-echo " First admin login:"
+echo " First admin login credentials are in ./config/sso-secrets.js:"
echo " user: ${ADMIN_UID}"
-echo " pass: ${ADMIN_PASS}"
+echo " pass: bootstrap.adminPass"
echo
echo " Proxy local admin (anti-lockout fallback if the SSO is unreachable):"
echo " user: proxyadmin2"
-echo " pass: ${PROXY_LOCAL_ADMIN_PASS}"
+echo " pass: auth.localAdminPass in ./config/proxy-secrets.js"
echo " (only shown when the account is first created; edit ./config/proxy-secrets.js"
echo " or use the proxy UI to change it afterward)"
echo
diff --git a/sso-manager-node b/sso-manager-node
index 37f2ece..5a8030f 160000
--- a/sso-manager-node
+++ b/sso-manager-node
@@ -1 +1 @@
-Subproject commit 37f2ece1724fdc22f271f86ab2100a2e620f952b
+Subproject commit 5a8030fd7d95edf83618b74ae05d790a62ccf940