Spun off from Copilot review on #436 (PR for #435).
Concern
The seeded landing page detects IDP availability via HEAD ./idp/register. When --idp is on, the IDP plugin handles that path. When --idp is off, the path falls through to the LDP catchall — usually 404 (no resource on disk), but if an operator has PUT a resource at /idp/register (e.g. via curl -X PUT for whatever reason), the LDP handler returns 200 for HEAD and the landing reveals Sign up + Sign in buttons that lead to a 404 when clicked.
Worst case is bad UX (revealed buttons that don't work), not a security issue.
Why this PR didn't address it
Possible approaches
- Reserved discovery endpoint — IDP plugin always registers
GET /.well-known/jss-idp (or similar) returning a small JSON { "register": "open" | "disabled" | "absent" }. When --no-idp, register the same path returning { "register": "absent" } so LDP can't shadow it. Script reads the JSON and decides. Pros: explicit, single request. Cons: new public-discovery endpoint to specify and maintain.
- Probe
/.well-known/openid-configuration first — if it 200s, IDP is on; then probe /idp/register for the 200/403 distinction. Two requests but uses an existing IDP route. Same operator-PUT shadowing risk applies to .well-known/openid-configuration though.
- Custom response header on IDP routes — e.g.
X-Powered-By: jss-idp. Script HEADs /idp/register, reveals only if the header is present. Requires touching every IDP handler or adding a hook.
- Reserve the
/idp/ namespace at the routing layer — refuse PUT/POST under /idp/* regardless of IDP enablement, so the LDP store can never shadow it. Independent of probe choice; simplest hardening; defends against the same class of bug for /idp/account/delete, /idp/credentials, etc.
(4) is probably the right load-bearing fix — the landing-page probe stops mattering because LDP can't ever shadow IDP paths. (1) becomes a nice-to-have on top.
Severity
Low. Buttons shown on a landing page lead to a 404 — a UX glitch on a deliberately oddly-configured server. Not a credential exposure or auth bypass.
Refs #436, #435.
Spun off from Copilot review on #436 (PR for #435).
Concern
The seeded landing page detects IDP availability via
HEAD ./idp/register. When--idpis on, the IDP plugin handles that path. When--idpis off, the path falls through to the LDP catchall — usually 404 (no resource on disk), but if an operator has PUT a resource at/idp/register(e.g. viacurl -X PUTfor whatever reason), the LDP handler returns 200 for HEAD and the landing reveals Sign up + Sign in buttons that lead to a 404 when clicked.Worst case is bad UX (revealed buttons that don't work), not a security issue.
Why this PR didn't address it
--no-idpand an operator-stored resource at exactly/idp/register— unlikely combination.Possible approaches
GET /.well-known/jss-idp(or similar) returning a small JSON{ "register": "open" | "disabled" | "absent" }. When--no-idp, register the same path returning{ "register": "absent" }so LDP can't shadow it. Script reads the JSON and decides. Pros: explicit, single request. Cons: new public-discovery endpoint to specify and maintain./.well-known/openid-configurationfirst — if it 200s, IDP is on; then probe/idp/registerfor the 200/403 distinction. Two requests but uses an existing IDP route. Same operator-PUT shadowing risk applies to.well-known/openid-configurationthough.X-Powered-By: jss-idp. Script HEADs/idp/register, reveals only if the header is present. Requires touching every IDP handler or adding a hook./idp/namespace at the routing layer — refuse PUT/POST under/idp/*regardless of IDP enablement, so the LDP store can never shadow it. Independent of probe choice; simplest hardening; defends against the same class of bug for/idp/account/delete,/idp/credentials, etc.(4) is probably the right load-bearing fix — the landing-page probe stops mattering because LDP can't ever shadow IDP paths. (1) becomes a nice-to-have on top.
Severity
Low. Buttons shown on a landing page lead to a 404 — a UX glitch on a deliberately oddly-configured server. Not a credential exposure or auth bypass.
Refs #436, #435.