Repository navigation
fix(runtime): standalone database-URL refusals name the scheme, never the URL - #23791
Conversation
The standalone host's unsupported-scheme refusal interpolated the whole database URL, so a password or token in OS_DATABASE_URL reached the console and the boot log. The retired in-memory engine refusal and the sqlite non-path refusal did the same. All three now name only the URL's scheme, which cannot hold a userinfo, host, path or query. Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 26 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1e8903aca348649aef25804a990c7248956e66a5 && git checkout 1e8903aca348649aef25804a990c7248956e66a5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8684079f2a4953b124a25cbb5542e83cf6c9dda4 c61b1366e1a36689f3400fc5762aa2ed7f56cae9 && git checkout -B drift-repro 8684079f2a4953b124a25cbb5542e83cf6c9dda4 && git merge --no-ff c61b1366e1a36689f3400fc5762aa2ed7f56cae9
node scripts/docs-audit/affected-docs.mjs --json 8684079f2a4953b124a25cbb5542e83cf6c9dda4 |
席位核对✅ ACCEPT: PR #23791 at
Checklist, read against GitHub and
The dev's open question (a door-level row in Out of scope, filed from the report:
Accepted deviations:
Landing: Generated by Claude Code |
Fixes #23764
Clause-②: no
What changed
The standalone host (
packages/runtime/src/standalone-stack.ts) now names only the URL's scheme in its database-URL refusals and never prints the URL. A password, a username or a query-string token inOS_DATABASE_URLno longer reaches the console, the boot log or the--jsonerror field.detectDriverFromUrl's unsupported-scheme refusal (the card) now readsUnsupported database URL scheme "redis://". Its supported-scheme list is unchanged. The wording follows the CLI host's refusal in PR fix(cli): os serve refuses an OS_DATABASE_URL whose scheme no driver recognises, and reads pg:// and a scheme-less path as the runtime host does #23761 (cli/serve --dev: an OS_DATABASE_URL whose scheme the CLI does not recognise is ignored in silence, and the boot serves an in-memory SQLite store instead of the database the operator named #23633).refuseRetiredMemoryEngine);sqliteFilenameFromUrl).urlSchemeOf, extracts the scheme. It uses the same regex the unsupported-scheme arm already tested. A scheme is letters, digits and+ - .up to://, so it cannot hold a userinfo, a host, a path or a query.Not changed:
Error).Not touched:
packages/cli,createStandaloneStack'smkdirSync(#23737 remains its own card), and every driver package.Why the scheme alone, not a redacted URL.
packages/runtimealready depends on@objectstack/spec, which exportsredactUrlCredentials. That helper keeps the username, and it strips only the query keys on its credential list. The pin requires that neither the username nor an unlisted token appear. The scheme cannot carry either one, and it is what the operator needs in order to fix the URL.Before and after, measured at the public doors
Built CLI and runtime at
c61b1366e1, with a plaindefineStackconfig andOS_DATABASE_URL=redis://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN. "Before" is the same build withstandalone-stack.tsreset to the baseec44cf60bfand the runtime rebuilt; it was restored afterwards (blob equal to HEAD,git diff HEADempty) and the runtime rebuilt again. The counts are occurrences in the door's whole stdout and stderr.os dev --no-watchos startos serveos migrate planos migrate recorded-by --json(stdouterrorfield)✗ [StandaloneStack] Unsupported database URL scheme: redis://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN. Supported schemes: …✗ [StandaloneStack] Unsupported database URL scheme "redis://". Supported schemes: …URL to message, per refusal (unit reading at the base, then at HEAD):
redis://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…?authToken=PLACEHOLDER-TOKENUnsupported database URL scheme: redis://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…Unsupported database URL scheme "redis://"https://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…(not a Turso host)Unsupported database URL scheme "https://"memory://PLACEHOLDER-USER:…asdatabaseUrlThe database URL "memory://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…"A database URL with the "memory://" schemememory://PLACEHOLDER-USER:…asOS_DATABASE_URLOS_DATABASE_URL "memory://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…"An OS_DATABASE_URL with the "memory://" schemememory://…(driver: memory, URL "memory://…")(driver: memory, URL scheme "memory://")postgres://PLACEHOLDER-USER:…withOS_DATABASE_DRIVER=sqlite…does not look like a file path: "postgres://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…"…does not look like a file path (its scheme is "postgres://")file:…does not look like a file path: "file:"…does not look like a file path (it names no file after "file:")Sibling messages in
standalone-stack.tsdetectDriverFromUrlunsupported-scheme refusalscheme://user:password@…?token=…refuseRetiredMemoryEngine: explicit, env and declared-datasource phrasesmemory://andmingo://accept anything after//; all three phrases echoed itsqliteFilenameFromUrlnon-path refusalOS_DATABASE_DRIVER=sqlitebeside a credentialedpostgres://URL echoed itunsupportedDriverMessage(theOS_DATABASE_DRIVERvalue)assertUrlNamedForRemoteDriverconsole.warn)throwMechanism readings
Database:row already redacts (redis://127.0.0.1:6399/0). The error has nocause, no door printed a stack, and no file written underOS_HOMEor the project carried a placeholder. After the change, every door counts 0. The pins also serialise every own property of the thrown error (message and stack) and find no placeholder.os dev,os start,os serve,os migrate planandos migrate recorded-by --json. A door-level pin would live inpackages/cli.packages/cli/test/migrate-exit-code.e2e.test.tsalready drivesos migrate recorded-by --jsonoverwat://nopeand readsUnsupported database URL schemefrom the payload, and that text is kept. This PR does not editpackages/cli, by the dispatch's ruling, so the door rows above are a one-time measurement, not a pin.Pins
packages/runtime/src/standalone-stack-url-refusal-credentials.test.ts(new, 30 cases):databaseUrloption,OS_DATABASE_URL, a declared default datasource,databaseDriver/OS_DATABASE_DRIVER), driven withPLACEHOLDER-USER:PLACEHOLDER-SECRET@…?authToken=PLACEHOLDER-TOKEN;Errorwith nocause, names the scheme and the remedy, and that no own property of the error carries any placeholder;postgres://,postgresql://,pg://,mysql://,mysql2://,mongodb://,mongodb+srv://,libsql://,https://*.turso.*,wasm-sqlite://,*.wasm.db,file:,sqlite://, a bare path, a scheme-less relative path,:memory:) on the same driver kind, and 3 rows show that a credentialedpostgres:///mysql:///mongodb://URL still resolves withurlbyte-identical.packages/runtime/src/standalone-stack.test.ts:memory://boot pin now expects"memory://" schemeand no URL tail (it used to pin the whole URL);createStandaloneStackover a credentialedredis://URL rejects, naming the scheme, with no placeholder anywhere on the error.Ablation
Run through
scripts/ablation-replace.mjs(wrap mode), with a shelltrapthat restores fromHEADand checks the blob hash. The suites import./standalone-stack.jsfromsrc, so nodist/leg applies.git diff HEADempty.schemeandselects. That went red on the wording assertion, not the credential one, so it proved nothing. It was redone with the wording kept (the row above).Verification at
c61b1366e1pnpm --filter @objectstack/runtime test(projectlocal): 399 files, 5979 passed, 19 skipped, exit 0.pnpm --filter @objectstack/runtime typecheck(build program plus test layer): exit 0. The test-layer ledger holds at 27 files / 190 errors.tsc --listFiles -p tsconfig.test.jsonincludes both changed test files.standalone-stack-url-refusal-credentials,standalone-stack,standalone-stack.libsql,standalone-stack.mysqlandresolve-project-database: 155 passed, exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 commands, all run, 62 exit 0.check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET, packages withoutdist/). It was re-run after those were built, and exited 0.--ranreconciliation: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN, all with recorded exit codes..tsfiles witheslint --no-inline-config: exit 0. Three pieces of evidence that the narrowing is sound:--print-configshows only per-file syntactic rules and no typed-lintparserOptions.project, so no other file can change these files' verdict.max-lenof 40 injected on the command line fires on all three files, so none is ignored.pnpm lintruns in CI'sLint & Repo Gates.test:repo(no repo-project test is changed), thepackages/clisuites (not touched), and the full lint farm.Acceptance notes
memory://refusal echoes the whole URL. It is inpackages/cli/src/utils/storage-driver.ts, theinferDriverTypeFromUrlmemory arm, which buildsOS_DATABASE_URL "…"from the raw value. Measured:OS_MODE=off OS_DATABASE_URL=memory://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN os serveexits 1 and prints each placeholder once. This is outside this card's file surface and is reported to the seat to file.file:case-sensitively, but detection is case-insensitive. Measured:OS_DATABASE_URL=FILE:./urd.db os servecreated a directory literally namedFILE:.in the working directory, with a SQLite file inside it. This is a different defect class from this card's, so it is not changed here and is reported to the seat to file.resolve-project-database.tsdocstring forretiredMemoryEngineMessagestill givesThe database URL "memory://"as an example selection phrase. It is a comment, outside this card's file surface, and left as it is.Generated by Claude Code