[Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

Description

@shinijimmy3

Version

1.61.1

Steps to reproduce

Steps to reproduce

Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

Minimal reproduction (Node, standalone request.newContext()):

  1. Generate a self-signed certificate with two CN attributes in the subject:

    openssl req -x509 -newkey rsa:2048 -nodes
    -keyout key.pem -out cert.pem -days 1
    -subj "/CN=localhost/CN=secondary-name"
    -addext "subjectAltName=DNS:localhost"

  2. server.mjs:

    import { createServer } from "node:https";
    import { readFileSync } from "node:fs";

    createServer(
    { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
    (req, res) => {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ ok: true }));
    }
    ).listen(18443, "127.0.0.1", () =>
    console.log("HTTPS server on https://localhost:18443")
    );

  3. repro.mjs:

    import { request } from "@playwright/test";

    const ctx = await request.newContext({ ignoreHTTPSErrors: true });
    const res = await ctx.get("https://localhost:18443/");
    console.log(res.status(), await res.text());
    await ctx.dispose();

  4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

Empirical verification matrix

PlaywrightCertificate subject/issuerResult
1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
1.61.1CN=localhostPASS: 200 OK
1.60.0CN=localhost, CN=secondaryPASS: 200 OK

Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

Root cause

Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

subjectName: peerCertificate.subject.CN as string,
issuer: peerCertificate.issuer.CN as string,

Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

Impact

Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

Expected behavior

Expected

200 {"ok":true} (this is what 1.60.0 does).

Actual behavior

Actual

apiRequestContext.get: response.securityDetails.issuer: expected string, got object
Call log:

  • -> GET https://localhost:18443/
    • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
  • <- 200 OK
    • content-type: application/json
    • ...

The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

Additional context

No response

Environment

System info
===========
- Playwright: 1.61.1
- Node.js: 22.14.0
- OS: Windows 10 (also observed in the same repro against a real backend on the same host)

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
     blocks
    (function() {
    function addCopyButtons() {
    document.querySelectorAll('pre code').forEach(function(codeBlock) {
    if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
    codeBlock.parentElement.setAttribute('data-copy-added', 'true');
    var btn = document.createElement('button');
    btn.textContent = 'Copy';
    btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
    btn.onmouseover = function() { this.style.opacity = '1'; };
    btn.onmouseout = function() { this.style.opacity = '0.7'; };
    btn.onclick = function() {
    navigator.clipboard.writeText(codeBlock.textContent).then(function() {
    btn.textContent = 'Copied!';
    setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
    });
    };
    codeBlock.parentElement.style.position = 'relative';
    codeBlock.parentElement.appendChild(btn);
    });
    }
    addCopyButtons();
    // Re-run on dynamic content
    var observer = new MutationObserver(addCopyButtons);
    observer.observe(document.body, { childList: true, subtree: true });
    })();
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

    Description

    @shinijimmy3

    Version

    1.61.1

    Steps to reproduce

    Steps to reproduce

    Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

    Minimal reproduction (Node, standalone request.newContext()):

    1. Generate a self-signed certificate with two CN attributes in the subject:

      openssl req -x509 -newkey rsa:2048 -nodes
      -keyout key.pem -out cert.pem -days 1
      -subj "/CN=localhost/CN=secondary-name"
      -addext "subjectAltName=DNS:localhost"

    2. server.mjs:

      import { createServer } from "node:https";
      import { readFileSync } from "node:fs";

      createServer(
      { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
      (req, res) => {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ ok: true }));
      }
      ).listen(18443, "127.0.0.1", () =>
      console.log("HTTPS server on https://localhost:18443")
      );

    3. repro.mjs:

      import { request } from "@playwright/test";

      const ctx = await request.newContext({ ignoreHTTPSErrors: true });
      const res = await ctx.get("https://localhost:18443/");
      console.log(res.status(), await res.text());
      await ctx.dispose();

    4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

    Empirical verification matrix

    PlaywrightCertificate subject/issuerResult
    1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
    1.61.1CN=localhostPASS: 200 OK
    1.60.0CN=localhost, CN=secondaryPASS: 200 OK

    Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

    page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

    Root cause

    Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

    subjectName: peerCertificate.subject.CN as string,
    issuer: peerCertificate.issuer.CN as string,
    

    Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

    Impact

    Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

    Expected behavior

    Expected

    200 {"ok":true} (this is what 1.60.0 does).

    Actual behavior

    Actual

    apiRequestContext.get: response.securityDetails.issuer: expected string, got object
    Call log:

    • -> GET https://localhost:18443/
      • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
    • <- 200 OK
      • content-type: application/json
      • ...

    The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

    Additional context

    No response

    Environment

    System info
    ===========
    - Playwright: 1.61.1
    - Node.js: 22.14.0
    - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

    Metadata

    Metadata

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

      Description

      @shinijimmy3

      Version

      1.61.1

      Steps to reproduce

      Steps to reproduce

      Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

      Minimal reproduction (Node, standalone request.newContext()):

      1. Generate a self-signed certificate with two CN attributes in the subject:

        openssl req -x509 -newkey rsa:2048 -nodes
        -keyout key.pem -out cert.pem -days 1
        -subj "/CN=localhost/CN=secondary-name"
        -addext "subjectAltName=DNS:localhost"

      2. server.mjs:

        import { createServer } from "node:https";
        import { readFileSync } from "node:fs";

        createServer(
        { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
        (req, res) => {
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ ok: true }));
        }
        ).listen(18443, "127.0.0.1", () =>
        console.log("HTTPS server on https://localhost:18443")
        );

      3. repro.mjs:

        import { request } from "@playwright/test";

        const ctx = await request.newContext({ ignoreHTTPSErrors: true });
        const res = await ctx.get("https://localhost:18443/");
        console.log(res.status(), await res.text());
        await ctx.dispose();

      4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

      Empirical verification matrix

      PlaywrightCertificate subject/issuerResult
      1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
      1.61.1CN=localhostPASS: 200 OK
      1.60.0CN=localhost, CN=secondaryPASS: 200 OK

      Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

      page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

      Root cause

      Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

      subjectName: peerCertificate.subject.CN as string,
      issuer: peerCertificate.issuer.CN as string,
      

      Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

      Impact

      Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

      Expected behavior

      Expected

      200 {"ok":true} (this is what 1.60.0 does).

      Actual behavior

      Actual

      apiRequestContext.get: response.securityDetails.issuer: expected string, got object
      Call log:

      • -> GET https://localhost:18443/
        • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
      • <- 200 OK
        • content-type: application/json
        • ...

      The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

      Additional context

      No response

      Environment

      System info
      ===========
      - Playwright: 1.61.1
      - Node.js: 22.14.0
      - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

      Metadata

      Metadata

      Labels

      Type

      No type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

        Description

        @shinijimmy3

        Version

        1.61.1

        Steps to reproduce

        Steps to reproduce

        Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

        Minimal reproduction (Node, standalone request.newContext()):

        1. Generate a self-signed certificate with two CN attributes in the subject:

          openssl req -x509 -newkey rsa:2048 -nodes
          -keyout key.pem -out cert.pem -days 1
          -subj "/CN=localhost/CN=secondary-name"
          -addext "subjectAltName=DNS:localhost"

        2. server.mjs:

          import { createServer } from "node:https";
          import { readFileSync } from "node:fs";

          createServer(
          { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
          (req, res) => {
          res.writeHead(200, { "Content-Type": "application/json" });
          res.end(JSON.stringify({ ok: true }));
          }
          ).listen(18443, "127.0.0.1", () =>
          console.log("HTTPS server on https://localhost:18443")
          );

        3. repro.mjs:

          import { request } from "@playwright/test";

          const ctx = await request.newContext({ ignoreHTTPSErrors: true });
          const res = await ctx.get("https://localhost:18443/");
          console.log(res.status(), await res.text());
          await ctx.dispose();

        4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

        Empirical verification matrix

        PlaywrightCertificate subject/issuerResult
        1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
        1.61.1CN=localhostPASS: 200 OK
        1.60.0CN=localhost, CN=secondaryPASS: 200 OK

        Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

        page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

        Root cause

        Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

        subjectName: peerCertificate.subject.CN as string,
        issuer: peerCertificate.issuer.CN as string,
        

        Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

        Impact

        Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

        Expected behavior

        Expected

        200 {"ok":true} (this is what 1.60.0 does).

        Actual behavior

        Actual

        apiRequestContext.get: response.securityDetails.issuer: expected string, got object
        Call log:

        • -> GET https://localhost:18443/
          • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
        • <- 200 OK
          • content-type: application/json
          • ...

        The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

        Additional context

        No response

        Environment

        System info
        ===========
        - Playwright: 1.61.1
        - Node.js: 22.14.0
        - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

        Metadata

        Metadata

        Labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

          Description

          @shinijimmy3

          Version

          1.61.1

          Steps to reproduce

          Steps to reproduce

          Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

          Minimal reproduction (Node, standalone request.newContext()):

          1. Generate a self-signed certificate with two CN attributes in the subject:

            openssl req -x509 -newkey rsa:2048 -nodes
            -keyout key.pem -out cert.pem -days 1
            -subj "/CN=localhost/CN=secondary-name"
            -addext "subjectAltName=DNS:localhost"

          2. server.mjs:

            import { createServer } from "node:https";
            import { readFileSync } from "node:fs";

            createServer(
            { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
            (req, res) => {
            res.writeHead(200, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ ok: true }));
            }
            ).listen(18443, "127.0.0.1", () =>
            console.log("HTTPS server on https://localhost:18443")
            );

          3. repro.mjs:

            import { request } from "@playwright/test";

            const ctx = await request.newContext({ ignoreHTTPSErrors: true });
            const res = await ctx.get("https://localhost:18443/");
            console.log(res.status(), await res.text());
            await ctx.dispose();

          4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

          Empirical verification matrix

          PlaywrightCertificate subject/issuerResult
          1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
          1.61.1CN=localhostPASS: 200 OK
          1.60.0CN=localhost, CN=secondaryPASS: 200 OK

          Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

          page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

          Root cause

          Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

          subjectName: peerCertificate.subject.CN as string,
          issuer: peerCertificate.issuer.CN as string,
          

          Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

          Impact

          Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

          Expected behavior

          Expected

          200 {"ok":true} (this is what 1.60.0 does).

          Actual behavior

          Actual

          apiRequestContext.get: response.securityDetails.issuer: expected string, got object
          Call log:

          • -> GET https://localhost:18443/
            • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
          • <- 200 OK
            • content-type: application/json
            • ...

          The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

          Additional context

          No response

          Environment

          System info
          ===========
          - Playwright: 1.61.1
          - Node.js: 22.14.0
          - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

          Metadata

          Metadata

          Labels

          Type

          No type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

            Description

            @shinijimmy3

            Version

            1.61.1

            Steps to reproduce

            Steps to reproduce

            Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

            Minimal reproduction (Node, standalone request.newContext()):

            1. Generate a self-signed certificate with two CN attributes in the subject:

              openssl req -x509 -newkey rsa:2048 -nodes
              -keyout key.pem -out cert.pem -days 1
              -subj "/CN=localhost/CN=secondary-name"
              -addext "subjectAltName=DNS:localhost"

            2. server.mjs:

              import { createServer } from "node:https";
              import { readFileSync } from "node:fs";

              createServer(
              { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
              (req, res) => {
              res.writeHead(200, { "Content-Type": "application/json" });
              res.end(JSON.stringify({ ok: true }));
              }
              ).listen(18443, "127.0.0.1", () =>
              console.log("HTTPS server on https://localhost:18443")
              );

            3. repro.mjs:

              import { request } from "@playwright/test";

              const ctx = await request.newContext({ ignoreHTTPSErrors: true });
              const res = await ctx.get("https://localhost:18443/");
              console.log(res.status(), await res.text());
              await ctx.dispose();

            4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

            Empirical verification matrix

            PlaywrightCertificate subject/issuerResult
            1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
            1.61.1CN=localhostPASS: 200 OK
            1.60.0CN=localhost, CN=secondaryPASS: 200 OK

            Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

            page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

            Root cause

            Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

            subjectName: peerCertificate.subject.CN as string,
            issuer: peerCertificate.issuer.CN as string,
            

            Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

            Impact

            Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

            Expected behavior

            Expected

            200 {"ok":true} (this is what 1.60.0 does).

            Actual behavior

            Actual

            apiRequestContext.get: response.securityDetails.issuer: expected string, got object
            Call log:

            • -> GET https://localhost:18443/
              • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
            • <- 200 OK
              • content-type: application/json
              • ...

            The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

            Additional context

            No response

            Environment

            System info
            ===========
            - Playwright: 1.61.1
            - Node.js: 22.14.0
            - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

            Metadata

            Metadata

            Labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

              Description

              @shinijimmy3

              Version

              1.61.1

              Steps to reproduce

              Steps to reproduce

              Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

              Minimal reproduction (Node, standalone request.newContext()):

              1. Generate a self-signed certificate with two CN attributes in the subject:

                openssl req -x509 -newkey rsa:2048 -nodes
                -keyout key.pem -out cert.pem -days 1
                -subj "/CN=localhost/CN=secondary-name"
                -addext "subjectAltName=DNS:localhost"

              2. server.mjs:

                import { createServer } from "node:https";
                import { readFileSync } from "node:fs";

                createServer(
                { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
                (req, res) => {
                res.writeHead(200, { "Content-Type": "application/json" });
                res.end(JSON.stringify({ ok: true }));
                }
                ).listen(18443, "127.0.0.1", () =>
                console.log("HTTPS server on https://localhost:18443")
                );

              3. repro.mjs:

                import { request } from "@playwright/test";

                const ctx = await request.newContext({ ignoreHTTPSErrors: true });
                const res = await ctx.get("https://localhost:18443/");
                console.log(res.status(), await res.text());
                await ctx.dispose();

              4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

              Empirical verification matrix

              PlaywrightCertificate subject/issuerResult
              1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
              1.61.1CN=localhostPASS: 200 OK
              1.60.0CN=localhost, CN=secondaryPASS: 200 OK

              Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

              page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

              Root cause

              Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

              subjectName: peerCertificate.subject.CN as string,
              issuer: peerCertificate.issuer.CN as string,
              

              Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

              Impact

              Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

              Expected behavior

              Expected

              200 {"ok":true} (this is what 1.60.0 does).

              Actual behavior

              Actual

              apiRequestContext.get: response.securityDetails.issuer: expected string, got object
              Call log:

              • -> GET https://localhost:18443/
                • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
              • <- 200 OK
                • content-type: application/json
                • ...

              The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

              Additional context

              No response

              Environment

              System info
              ===========
              - Playwright: 1.61.1
              - Node.js: 22.14.0
              - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

              Metadata

              Metadata

              Labels

              Type

              No type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                [Bug]: APIRequestContext fails on any HTTPS response whose certificate has a multi-value RDN (regression in 1.61.0) #41815

                Description

                @shinijimmy3

                Version

                1.61.1

                Steps to reproduce

                Steps to reproduce

                Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.

                Minimal reproduction (Node, standalone request.newContext()):

                1. Generate a self-signed certificate with two CN attributes in the subject:

                  openssl req -x509 -newkey rsa:2048 -nodes
                  -keyout key.pem -out cert.pem -days 1
                  -subj "/CN=localhost/CN=secondary-name"
                  -addext "subjectAltName=DNS:localhost"

                2. server.mjs:

                  import { createServer } from "node:https";
                  import { readFileSync } from "node:fs";

                  createServer(
                  { key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
                  (req, res) => {
                  res.writeHead(200, { "Content-Type": "application/json" });
                  res.end(JSON.stringify({ ok: true }));
                  }
                  ).listen(18443, "127.0.0.1", () =>
                  console.log("HTTPS server on https://localhost:18443")
                  );

                3. repro.mjs:

                  import { request } from "@playwright/test";

                  const ctx = await request.newContext({ ignoreHTTPSErrors: true });
                  const res = await ctx.get("https://localhost:18443/");
                  console.log(res.status(), await res.text());
                  await ctx.dispose();

                4. npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs

                Empirical verification matrix

                PlaywrightCertificate subject/issuerResult
                1.61.1CN=localhost, CN=secondaryFAIL: response.securityDetails.issuer: expected string, got object
                1.61.1CN=localhostPASS: 200 OK
                1.60.0CN=localhost, CN=secondaryPASS: 200 OK

                Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.

                page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.

                Root cause

                Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:

                subjectName: peerCertificate.subject.CN as string,
                issuer: peerCertificate.issuer.CN as string,
                

                Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.

                Impact

                Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.

                Expected behavior

                Expected

                200 {"ok":true} (this is what 1.60.0 does).

                Actual behavior

                Actual

                apiRequestContext.get: response.securityDetails.issuer: expected string, got object
                Call log:

                • -> GET https://localhost:18443/
                  • user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
                • <- 200 OK
                  • content-type: application/json
                  • ...

                The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.

                Additional context

                No response

                Environment

                System info
                ===========
                - Playwright: 1.61.1
                - Node.js: 22.14.0
                - OS: Windows 10 (also observed in the same repro against a real backend on the same host)

                Metadata

                Metadata

                Labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions