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"]
}
  • source is a Playwright storageState JSON file (cookies + origins[]/localStorage).
  • origins are exact origins (scheme + host + optional port). Only entries whose cookie domain exactly matches an authorized host (or its dot-prefixed spelling) and origins whose origin exactly 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:

  1. 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.json now holds the cookies + localStorage for the sites you visited.)

  2. 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.