security: mandatory auth for the in-process Studio console + session cookie - #168
Open
russimicro wants to merge 1 commit into
Open
security: mandatory auth for the in-process Studio console + session cookie#168russimicro wants to merge 1 commit into
russimicro wants to merge 1 commit into
Conversation
…cookie
**Problem 1 — AdsStudioStart() bound an open listener by default.**
HttpConsole::add_user() already existed (studio.web.0.8, wired into
openads_serverd via config.ini), but the in-process/LocalServer entry
point (AdsStudioStart, used by any app embedding ace64.dll/openace64.dll
directly) only called add_user() *if* OPENADS_STUDIO_USER/PASSWORD
happened to be set — bind-first, auth-optional. Any other process on the
box that loads the DLL and calls AdsStudioStart() without setting those
two env vars got a fully open console: read/edit/delete on every table
in the given data dir, no credentials asked. A host's own gate (a
license check, say) can't protect an export any caller can invoke
directly (ctypes / P-Invoke / a 10-line .c) — the DLL itself has to
refuse. AdsStudioStart() now returns AE_LOGIN_FAILED (7077) instead of
binding when the env vars are absent; try_auto_start() (the
OPENADS_STUDIO_PORT env-var hook fired from DllMain) got the same rule,
so the auto-start convenience path can't create an unauthenticated
listener either. No caller in this repo relies on the old no-auth
default from AdsStudioStart specifically.
**Problem 2 — Basic-auth via URL userinfo breaks the SPA's own fetch().**
The natural way to hand a generated-per-session credential to a webview
host is `http://user:pass@host:port/...`. It works for the initial
navigation, but once `document.location` carries userinfo, the Fetch
spec refuses to construct a Request from any URL resolved against it
("Request cannot be constructed from a URL that includes credentials"),
so the console's own `fetch("/api/...")` calls throw immediately after
load. Fix: a one-time login path, `GET /?_auth_user=U&_auth_pass=P`
(query string, not userinfo — unaffected by the restriction above,
since it's a top-level navigation, not a fetch()). On a match the
server sets an HttpOnly `oads_auth` session cookie (SameSite=Strict)
and 302-redirects to the same view without the credentials in the URL;
from there every request (page loads and fetch alike) authenticates via
the cookie automatically, same-origin, no JS changes required. Wrong
credentials get a 403, not a hang or a silent no-op.
**Audit trail.** Every failed login and every request rejected by the
auth gate now goes through openads::mgmt::ErrorLog (the same
ads_err.dbf the rest of the engine already writes to, readable via
sp_mgGetErrorLog) with the client IP, method and path — a rejected
attempt is never silent.
Verified: full unit suite green (1324/1324, 16 skipped — unrelated
missing fixtures) on this branch. Manually exercised end-to-end against
a FiveWin/WebView2 host (query-param login -> cookie -> SPA loads and
its fetch() calls succeed; AdsStudioStart without the env vars set
returns AE_LOGIN_FAILED and binds nothing).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenADS Studio — mandatory auth for the in-process (LocalServer) console, plus a session cookie that doesn't break the SPA's own
fetch()rddads+ OpenADS x64)src/abi/studio_embed.cpp,tools/serverd/http_server.cppContext
We embed the Studio console in-process inside a small FiveWin/WebView2 shell (
AdsStudioStart(port, dataDir), theLocalServerentry point — noopenads_serverdinvolved). Building a security layer around that shell surfaced two issues in the shared code, independent of our own host.Problem 1 —
AdsStudioStart()bound an open listener by defaultHttpConsole::add_user()already existed (studio.web.0.8, wired intoopenads_serverdvia itsconfig.ini), but the in-process entry point only called it ifOPENADS_STUDIO_USER/OPENADS_STUDIO_PASSWORDhappened to be set — bind first, auth optional.Concretely: any other process on the same machine that loads
ace64.dll/openace64.dlland callsAdsStudioStart()without setting those two env vars gets a fully open console — read/edit/delete on every table under the given data dir, no credentials asked, no warning. A host's own access-control layer (a license check, a login screen) can't protect an exported DLL function that any caller can invoke directly —ctypesin Python, P/Invoke in C#/PowerShell, or a ten-line.cfile linking the import lib.Fix
AdsStudioStart()now returnsAE_LOGIN_FAILED(7077) instead of binding when the env vars are absent.try_auto_start()(theOPENADS_STUDIO_PORTenv-var hook fired fromDllMain) got the identical rule, so the auto-start convenience path can't create an unauthenticated listener either.This is a behavior change: a caller that previously relied on the no-auth default for the in-process console needs to start setting
OPENADS_STUDIO_USER/OPENADS_STUDIO_PASSWORD. We checked — nothing in this repo (source, tests, examples, docs samples) callsAdsStudioStartexpecting the old default, but flagging this explicitly since it's the kind of change that deserves a maintainer's eyes before merging, not just a green CI run.Problem 2 — Basic-auth via URL userinfo breaks the SPA's own
fetch()The obvious way to hand a generated per-session credential to an embedding webview host is
http://user:pass@host:port/.... It works for the first navigation (the browser sendsAuthorization: Basicfor that request), but oncedocument.locationcarries userinfo, the Fetch spec refuses to construct aRequestfrom any URL resolved against it:So the console's own
fetch("/api/...")calls (used everywhere in the SPA — table list, browse, structure, SQL) throw immediately after the page loads. Confirmed against WebView2/Chromium; this isn't an OpenADS bug per se, it's a spec restriction that any embedding host hits the moment it tries the "obvious" URL-credential approach.Fix
A one-time login path:
GET /?_auth_user=U&_auth_pass=P(query string, not userinfo — a top-level navigation, not afetch(), so it's unaffected by the restriction above). On a credential match the server:HttpOnly,SameSite=Strictsession cookie (oads_auth, same base64user:passblob Basic-auth already used internally —user_pass_valid()verifies both the header and the cookie with one code path).302-redirects to the same view (table/tab/hostparams preserved) without the credentials in the URL, so they never end up indocument.locationfor the SPA's own JS to trip over.From there every request — page loads and
fetch()alike — authenticates via the cookie automatically (same-origin, no JS changes needed anywhere in the SPA). Wrong credentials get a403, not a hang or a silent pass-through.Audit trail
Every failed login attempt and every request rejected by the auth gate now goes through
openads::mgmt::ErrorLog::instance().log(...)— the sameads_err.dbfthe rest of the engine already writes to (readable viasp_mgGetErrorLog), taggedSTUDIO_AUTH, with the client IP, method and path. A rejected attempt is never silent.Verification
origin/main+ these two files only).openads_unit_tests(src/CMakeLists.txtonly addsabi/studio_embed.cppandtools/serverd/http_server.cppto theopenads_ace/openads_serverdtargets, notopenads_core) — mechanically, this change cannot regress anything the existing suite already exercises.AdsStudioStartwithout the env vars set returnsAE_LOGIN_FAILEDand binds nothing; with them set, the query-param login → cookie → SPA loads and itsfetch()calls succeed; wrong credentials on the login endpoint get403; an unauthenticated direct request to any/api/*route gets401and lands inads_err.dbf.Scope note
This PR intentionally does not touch anything about
openads_serverd's ownconfig.ini-based auth (auth_user/http_user) — that path never goes throughAdsStudioStart, callsHttpConsole::add_user()directly from its ownmain.cpp, and stays exactly as it was (auth optional, operator's decision). This PR is scoped to the in-process/LocalServerentry point only.