Owner-authorized login-state injection (WP11)
August 31, 2026 ยท View on GitHub
dsh-qa can start a browser QA session with pre-loaded login state (cookies + localStorage) for EXPLICITLY authorized origins only, from an explicit state file the owner exports themselves. It never scans for profiles, never reads a default profile location, and never accepts a directory.
Session config
qa_session_start (and the scenario target.loginState) accept:
{
"source": "/absolute/path/to/state.json",
"origins": ["https://app.example.com"]
}
sourceis a Playwright storageState JSON file (cookies+origins[]/localStorage).originsare exact origins (scheme + host + optional port). Only entries whose cookiedomainexactly matches an authorized host (or its dot-prefixed spelling) and origins whoseoriginexactly matches the list are injected.- The file is read, filtered in memory, injected, then dropped โ never copied into the session profile. A file that fails to parse, has no authorized entries, or holds entries the filter cannot classify fails the start.
Producing a storageState file with Playwright
The owner exports the state file themselves; dsh-qa never writes one. Two common ways:
-
Codegen + save state: run Playwright Codegen against the real site, sign in, then save the authenticated context:
npx playwright codegen --save-storage=state.json https://app.example.com/login(Sign in in the opened browser window; close it;
state.jsonnow holds the cookies + localStorage for the sites you visited.) -
A small script that launches a persistent context, signs in, and writes
storageState():import { chromium } from 'playwright' const browser = await chromium.launchPersistentContext('', { headless: false }) const page = browser.pages()[0] ?? await browser.newPage() await page.goto('https://app.example.com/login') // ... sign in manually ... await page.context().storageState({ path: 'state.json' }) await browser.close()
Secrets in state.json are handled by dsh-qa's fail-closed redaction: the
file's contents never reach reports or error messages (only counts and
origins are named), and a redaction corpus test asserts a cookie value never
appears in report.json / report.md / report.jsonl.