{"metadata": {"vulnerability": "CVE-2026-76024", "count": 28, "kinds": {"milestone": 7, "sighting": 6, "kev": 0, "other": 15}}, "data": [{"timestamp": "2026-08-18T19:28:13.970Z", "date_only": false, "value": "2026-08-18T19:28:13.970Z", "kind": "milestone", "label": "Reserved", "source": "cvelistv5", "record": "CVE-2026-76024", "field": "cveMetadata.dateReserved"}, {"timestamp": "2026-10-29T00:00:00Z", "date_only": true, "value": "2026-10-29", "kind": "milestone", "label": "Published", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "vendor_advisories.0.published_at", "linked_record": true}, {"timestamp": "2026-10-29T20:00:00Z", "date_only": false, "value": "2026-10-29T20:00:00.000000Z", "kind": "sighting", "label": "Seen", "source": "https://www.hkcert.org/security-bulletin/google-chrome-multiple-vulnerabilities_20261030", "record": "CVE-2026-76024", "field": "creation_timestamp", "uuid": "f620704f-4338-4471-ac88-f418a14b269e", "url": "https://www.hkcert.org/security-bulletin/google-chrome-multiple-vulnerabilities_20261030"}, {"timestamp": "2026-10-30T00:00:00Z", "date_only": false, "value": "2026-10-30T00:00:00", "kind": "other", "label": "Last revision date", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "last_revision_date", "linked_record": true}, {"timestamp": "2026-10-30T00:00:00Z", "date_only": false, "value": "2026-10-30T00:00:00", "kind": "milestone", "label": "Published", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "initial_release_date", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, {"timestamp": "2026-10-30T00:00:00Z", "date_only": false, "value": "2026-10-30T00:00:00.000000", "kind": "other", "label": "Revision date", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "revisions.0.revision_date", "linked_record": true}, {"timestamp": "2026-10-30T00:05:07.416Z", "date_only": false, "value": "2026-10-30T00:05:07.416Z", "kind": "milestone", "label": "Published", "source": "cvelistv5", "record": "CVE-2026-76024", "field": "cveMetadata.datePublished", "metric_role": "disclosure", "metric_basis": "recorded_event"}, {"timestamp": "2026-10-30T00:57:52.125Z", "date_only": false, "value": "2026-10-30T00:57:52.125271Z", "kind": "sighting", "label": "Seen", "source": "https://infosec.exchange/users/cve/statuses/117527142173350201", "record": "CVE-2026-76024", "field": "creation_timestamp", "uuid": "55b87917-4894-407e-b747-a7513cecf09b", "content": "CVE-2026-76024  Memory corruption in MediaStream in Google Chrome prior to 156.0.8078.79 allowed a remote attacker to potentially read memory inside the sandbox via a crafted HTML page. (Chromium security severity: Critical)\nhttps://www.cve.org/CVERecord?id=CVE-2026-76024https://chromereleases.googleblog.com/2026/10/stable-channel-update-for-desktop_02107719452.htmlhttps://issues.chromium.org/issues/576215540#chromium #Google #Chrome#CVE_2026_76024#bot", "url": "https://infosec.exchange/users/cve/statuses/117527142173350201"}, {"timestamp": "2026-10-30T01:16:46Z", "date_only": false, "value": "2026-10-30T01:16:46Z", "kind": "other", "label": "Nvd published at", "source": "github \u00b7 GHSA-CPV7-4G36-WQCC", "record": "GHSA-CPV7-4G36-WQCC", "field": "database_specific.nvd_published_at", "linked_record": true}, {"timestamp": "2026-10-30T01:16:46.840Z", "date_only": false, "value": "2026-10-30T01:16:46.840", "kind": "milestone", "label": "Published", "source": "fkie_nvd \u00b7 FKIE_CVE-2026-76024", "record": "FKIE_CVE-2026-76024", "field": "published", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, {"timestamp": "2026-10-30T01:30:27.123Z", "date_only": false, "value": "2026-10-30T01:30:27.123464Z", "kind": "sighting", "label": "Seen", "source": "https://infosec.exchange/users/offseq/statuses/117527270279264067", "record": "cve-2026-76024", "field": "creation_timestamp", "uuid": "9681fe5c-b72f-45b5-8121-5d12e3729566", "content": "CVE-2026-76024: CRITICAL Chrome MediaStream memory corruption vuln lets remote attackers read sandboxed memory. Update Chrome to 156.0.8078.79+ to mitigate. Details: https://radar.offseq.com/threat/cve-2026-76024-memory-corruption-in-google-chrome-8c5b94159c077f18 #OffSeq #Vuln #GoogleChrome #AppSec", "url": "https://infosec.exchange/users/offseq/statuses/117527270279264067"}, {"timestamp": "2026-10-30T01:30:29.127Z", "date_only": false, "value": "2026-10-30T01:30:29.127053Z", "kind": "sighting", "label": "Seen", "source": "https://bsky.app/profile/offseq.bsky.social/post/3mz2myvm57y2q", "record": "cve-2026-76024", "field": "creation_timestamp", "uuid": "7562a6c6-6b60-4dc1-8e4c-e8f44d297192", "content": "CRITICAL memory corruption in Chrome (pre-156.0.8078.79) enables remote attackers to read browser sandbox memory. Patch immediately: https://radar.offseq.com/threat/cve-2026-76024-memory-corruption-in-google-chrome-8c5b94159c077f18 #OffSeq #GoogleChrome #Vulnerability", "url": "https://bsky.app/profile/offseq.bsky.social/post/3mz2myvm57y2q"}, {"timestamp": "2026-10-30T01:44:27.578Z", "date_only": false, "value": "2026-10-30T01:44:27.578472Z", "kind": "sighting", "label": "Seen", "source": "https://bsky.app/profile/securityonline.bsky.social/post/3mz2nrvqmcb2y", "record": "CVE-2026-76024", "field": "creation_timestamp", "uuid": "bac8c514-82ec-409d-98af-d64fb1938af5", "content": "Chrome 156 security update 156.0.8078.79 fixes 21 flaws, including critical Chrome vulnerability CVE-2026-76024 in MediaStream. Update now.\n\n#Chrome #GoogleChrome #Chrome156 #BrowserSecurity #CVE202676024 #SecurityUpdate #PatchNow #Vulnerability", "url": "https://bsky.app/profile/securityonline.bsky.social/post/3mz2nrvqmcb2y"}, {"timestamp": "2026-10-30T03:32:01Z", "date_only": false, "value": "2026-10-30T03:32:01Z", "kind": "milestone", "label": "Published", "source": "github \u00b7 GHSA-CPV7-4G36-WQCC", "record": "GHSA-CPV7-4G36-WQCC", "field": "published", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, {"timestamp": "2026-10-30T13:11:04.883Z", "date_only": false, "value": "2026-10-30T13:11:04.883234Z", "kind": "other", "label": "Recorded", "source": "cvelistv5 / CISA-ADP", "record": "CVE-2026-76024", "field": "containers.adp.0.metrics.1.other.content.timestamp"}, {"timestamp": "2026-10-30T13:11:04.883Z", "date_only": false, "value": "2026-10-30T13:11:04.883234Z", "kind": "other", "label": "Recorded", "source": "fkie_nvd \u00b7 FKIE_CVE-2026-76024", "record": "FKIE_CVE-2026-76024", "field": "metrics.ssvcV203.0.ssvcData.timestamp", "linked_record": true}, {"timestamp": "2026-10-30T13:11:25.487Z", "date_only": false, "value": "2026-10-30T13:11:25.487Z", "kind": "other", "label": "Updated", "source": "cvelistv5 / CISA-ADP", "record": "CVE-2026-76024", "field": "containers.adp.0.providerMetadata.dateUpdated"}, {"timestamp": "2026-10-30T15:34:29Z", "date_only": false, "value": "2026-10-30T15:34:29Z", "kind": "other", "label": "Updated", "source": "github \u00b7 GHSA-CPV7-4G36-WQCC", "record": "GHSA-CPV7-4G36-WQCC", "field": "modified", "linked_record": true}, {"timestamp": "2026-11-01T00:00:00Z", "date_only": false, "value": "2026-11-01T00:00:00Z", "kind": "other", "label": "Advisory revision", "source": "csaf_opensuse \u00b7 OPENSUSE-SU-2026:12377-1", "record": "OPENSUSE-SU-2026:12377-1", "field": "document.tracking.revision_history.0.date", "linked_record": true}, {"timestamp": "2026-11-01T00:00:00Z", "date_only": false, "value": "2026-11-01T00:00:00Z", "kind": "other", "label": "Document generated", "source": "csaf_opensuse \u00b7 OPENSUSE-SU-2026:12377-1", "record": "OPENSUSE-SU-2026:12377-1", "field": "document.tracking.generator.date", "linked_record": true}, {"timestamp": "2026-11-01T00:00:00Z", "date_only": false, "value": "2026-11-01T00:00:00Z", "kind": "other", "label": "Impact", "source": "csaf_opensuse \u00b7 OPENSUSE-SU-2026:12377-1", "record": "OPENSUSE-SU-2026:12377-1", "field": "vulnerabilities.0.threats.0.date", "linked_record": true}, {"timestamp": "2026-11-01T00:00:00Z", "date_only": false, "value": "2026-11-01T00:00:00Z", "kind": "milestone", "label": "Published", "source": "csaf_opensuse \u00b7 OPENSUSE-SU-2026:12377-1", "record": "OPENSUSE-SU-2026:12377-1", "field": "document.tracking.initial_release_date", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, {"timestamp": "2026-11-03T16:09:23Z", "date_only": false, "value": "2026-11-03T16:09:23.000000Z", "kind": "sighting", "label": "Seen", "source": "https://gist.github.com/mrcointreau/f5d7de316059888275a16060ce02157a", "record": "CVE-2026-76024", "field": "creation_timestamp", "uuid": "5fbfb601-7715-4201-99a0-23b724ed2e83", "content": "#!/usr/bin/env bash\n#\n# playwright-chrome-audit.sh\n#\n# Finds every Playwright install and every Chromium-family browser it can see\n# on this machine, and checks each browser against the current Chrome release.\n#\n# Works on macOS and Linux with nothing to install (bash 3.2+, curl).\n#\n#   bash playwright-chrome-audit.sh              # home folder + system locations\n#   bash playwright-chrome-audit.sh --quick      # browser caches only, in seconds\n#   bash playwright-chrome-audit.sh --all        # the whole disk\n#   bash playwright-chrome-audit.sh ~/work /data # also search these folders\n#   sudo bash playwright-chrome-audit.sh --all   # include other users\n#\n# It is read-only: it deletes and changes nothing, and it never runs a browser\n# or anything else it finds. Versions are read from the files themselves.\n#\n# How a browser is judged:\n#   VULNERABLE  older than a fix listed in KNOWN_FIXES below\n#   OUTDATED    newer than those, but behind today's Chrome stable\n#   PRERELEASE  a frozen pre-release build, so its fixes cannot be confirmed\n#   OK          at Chrome stable or ahead, or a self-updating beta or canary\n#   UNKNOWN     its version could not be read\n#\n# \"Chrome stable\" comes live from Google's Chrome for Testing feed, so the\n# script stays useful without edits. KNOWN_FIXES adds detail, and is the\n# fallback when the feed cannot be reached.\n#\n# Exit codes:\n#   0   nothing needs attention\n#   1   a vulnerable or outdated browser was found\n#   2   nothing known to be bad, but the run could not confirm everything\n#   64  bad arguments\n\n[ -n \"${BASH_VERSION:-}\" ] || exec bash \"$0\" \"$@\"\nset +o posix 2&gt;/dev/null\nexport LC_ALL=C\n\n# ---- Update these when new Chrome fixes matter --------------------------------\nBASELINE_DATE=2026-11-03\n# fixed-in version | kind (exploited, critical, macos) | what is missing\nKNOWN_FIXES=(\n  \"152.0.7977.82|exploited|CVE-2026-85046\"\n  \"153.0.8010.36|exploited|CVE-2026-87491\"\n  \"155.0.8059.39|macos|CVE-2026-106378 (sandbox escape)\"\n  \"156.0.8078.47|critical|CVE-2026-113303\"\n  \"156.0.8078.73|critical|CVE-2026-118306 (sandbox escape)\"\n  \"156.0.8078.79|critical|CVE-2026-76024\"\n)\n# -------------------------------------------------------------------------------\n\nCFT_URL=https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json\nNPM_URL=https://registry.npmjs.org/playwright-core/latest\nPW_RAW=https://raw.githubusercontent.com/microsoft/playwright\n\nMODE=default OFFLINE=0 QUIET=0 MIN_VERSION='' INCOMPLETE=0\nEXTRA_ROOTS=()\nROOTS=()\n\nusage() {\n  cat &lt;&lt;'EOF'\nUsage: playwright-chrome-audit.sh [options] [folder ...]\n\n  --quick             Only look in browser caches and what they point to\n  --all               Search the whole disk (slow). External drives are skipped;\n                      name them as folders to include them\n  --min-version VER   Treat VER and newer as OK. This replaces every other check,\n                      including the list of known bugs\n  --offline           No network; compare against the built-in list only\n  --quiet             Only print problems and the result\n  -h, --help          Show this help\n\nFolders given as arguments are searched as well.\nExit codes: 0 clean, 1 vulnerable or outdated, 2 could not confirm everything,\n64 bad arguments.\nEOF\n}\n\nwhile [ $# -gt 0 ]; do\n  case $1 in\n    --quick) MODE=quick ;;\n    --all) MODE=all ;;\n    --offline) OFFLINE=1 ;;\n    --quiet | -q) QUIET=1 ;;\n    --min-version)\n      [ $# -ge 2 ] || { echo \"--min-version needs a version\" &gt;&amp;2; exit 64; }\n      MIN_VERSION=$2; shift ;;\n    --min-version=*) MIN_VERSION=${1#*=} ;;\n    -h | --help) usage; exit 0 ;;\n    -*) echo \"Unknown option: $1 (try --help)\" &gt;&amp;2; exit 64 ;;\n    *)\n      [ -d \"$1\" ] || { echo \"Not a folder: $1\" &gt;&amp;2; exit 64; }\n      dir=${1%/}\n      dir=${dir:-/}\n      case $dir in\n        # The search skips folders with these names, so start from their contents.\n        */Library/CloudStorage | */Library/Mobile\\ Documents) EXTRA_ROOTS+=(\"$dir\"/*) ;;\n        *) EXTRA_ROOTS+=(\"$dir\") ;;\n      esac ;;\n  esac\n  shift\ndone\n\nOS=$(uname -s)\nIS_ROOT=0\n[ \"$(id -u)\" != 0 ] || IS_ROOT=1\nT=$(mktemp -d \"${TMPDIR:-/tmp}/pw-audit.XXXXXX\") || { echo \"Cannot create a temp folder\" &gt;&amp;2; exit 2; }\ntrap 'rm -rf \"$T\"' EXIT\nSEP=$'\\037'\n: &gt;\"$T/apps\"; : &gt;\"$T/bins\"; : &gt;\"$T/pkgdirs\"; : &gt;\"$T/archives\"; : &gt;\"$T/limits\"\n: &gt;\"$T/browsers\"; : &gt;\"$T/packages\"; : &gt;\"$T/revmap\"; : &gt;\"$T/disk_revs\"; : &gt;\"$T/images\"\n\nif [ -t 1 ] &amp;&amp; [ -z \"${NO_COLOR:-}\" ]; then\n  C_RED=$'\\033[31m' C_YEL=$'\\033[33m' C_GRN=$'\\033[32m' C_BLD=$'\\033[1m' C_DIM=$'\\033[2m' C_OFF=$'\\033[0m'\nelse\n  C_RED='' C_YEL='' C_GRN='' C_BLD='' C_DIM='' C_OFF=''\nfi\n\nheading() { printf '\\n%s%s%s\\n' \"$C_BLD\" \"$1\" \"$C_OFF\"; }\nnote() { [ \"$QUIET\" = 1 ] || printf '%s\\n' \"$1\"; }\n\n# Something this run did not cover. incomplete() also rules out a clean result.\nlimit() { printf '  %s\\n' \"$1\" &gt;&gt;\"$T/limits\"; }\nincomplete() { INCOMPLETE=1; limit \"$1\"; }\n\n# Drops control characters, so a crafted package or folder name cannot rewrite\n# the report or shift its columns.\nclean() { tr -d '\\000-\\011\\013-\\037\\177'; }\n\n# ---- Versions -----------------------------------------------------------------\n\nis_version() {\n  case $1 in '' | *[!0-9.]* | .* | *. | *..*) return 1 ;; esac\n  case $1 in *.*.*.*) return 0 ;; esac\n  return 1\n}\n\n# True when version $1 is older than version $2.\nver_lt() {\n  local IFS=. i x y\n  local -a a b\n  # shellcheck disable=SC2206  # splitting on dots is the point\n  a=($1) b=($2)\n  for i in 0 1 2 3; do\n    x=${a[i]:-0} y=${b[i]:-0}\n    [ \"$((10#$x))\" -lt \"$((10#$y))\" ] &amp;&amp; return 0\n    [ \"$((10#$x))\" -gt \"$((10#$y))\" ] &amp;&amp; return 1\n  done\n  return 1\n}\n\nmajor() { printf '%s' \"${1%%.*}\"; }\n\n# Chrome ships pairs such as .79/.80 for the same code. Treat the lower as equal.\nsame_release() {\n  [ \"${1%.*}\" = \"${2%.*}\" ] &amp;&amp; [ \"$((10#${1##*.} + 1))\" -ge \"$((10#${2##*.}))\" ]\n}\n\nbaseline() {\n  local row fixed rest kind best=0.0.0.0\n  for row in \"${KNOWN_FIXES[@]}\"; do\n    fixed=${row%%|*} rest=${row#*|}\n    kind=${rest%%|*}\n    if [ \"$kind\" = macos ] &amp;&amp; [ \"$OS\" != Darwin ]; then continue; fi\n    ver_lt \"$best\" \"$fixed\" &amp;&amp; best=$fixed\n  done\n  printf '%s' \"$best\"\n}\n\n# Lists the known fixes a version is missing, worst first.\nmissing_fixes() {\n  local v=$1 row fixed rest kind what exploited='' others=''\n  for row in \"${KNOWN_FIXES[@]}\"; do\n    fixed=${row%%|*} rest=${row#*|}\n    kind=${rest%%|*} what=${rest#*|}\n    ver_lt \"$v\" \"$fixed\" || continue\n    if [ \"$kind\" = macos ] &amp;&amp; [ \"$OS\" != Darwin ]; then continue; fi\n    if [ \"$kind\" = exploited ]; then\n      exploited=\"$exploited${exploited:+, }$what\"\n    else\n      others=\"$others${others:+, }$what\"\n    fi\n  done\n  [ -z \"$exploited\" ] || printf 'exploited in the wild: %s. ' \"$exploited\"\n  [ -z \"$others\" ] || printf 'Also missing: %s.' \"$others\"\n}\n\n# Sets STATUS and WHY for a browser version.\nclassify() {\n  local v=$1\n  WHY=''\n  if ! is_version \"$v\"; then STATUS=UNKNOWN WHY=\"version could not be read\"; return; fi\n  if [ -n \"$MIN_VERSION\" ]; then\n    if ver_lt \"$v\" \"$MIN_VERSION\"; then\n      STATUS=VULNERABLE WHY=\"below your minimum $MIN_VERSION\"\n    else\n      STATUS=OK\n    fi\n    return\n  fi\n  if ver_lt \"$v\" \"$BASELINE\"; then\n    STATUS=VULNERABLE WHY=$(missing_fixes \"$v\")\n    return\n  fi\n  if ver_lt \"$v\" \"$REF_VERSION\"; then\n    if same_release \"$v\" \"$REF_VERSION\"; then\n      STATUS=OK WHY=\"one build behind stable $REF_VERSION; Chrome ships builds in pairs\"\n    else\n      STATUS=OUTDATED WHY=\"behind Chrome stable $REF_VERSION, so it may lack newer fixes\"\n    fi\n    return\n  fi\n  if [ \"$(major \"$v\")\" -gt \"$(major \"$REF_VERSION\")\" ]; then\n    STATUS=PRERELEASE WHY=\"newer than stable, but a frozen pre-release build can lag on fixes\"\n    return\n  fi\n  STATUS=OK\n}\n\nrank() {\n  case $1 in\n    VULNERABLE) echo 1 ;; OUTDATED) echo 2 ;; UNKNOWN) echo 3 ;;\n    PRERELEASE | \"NOT DOWNLOADED\") echo 4 ;; INFO) echo 6 ;; *) echo 7 ;;\n  esac\n}\n\n# ---- Reading files ------------------------------------------------------------\n\n# One JSON object per line, so sed can pick fields without a JSON parser.\njson_objects() { tr -d '\\r\\n' | sed 's/\"revisionOverrides\"[^}]*}//g' | tr '{}' '\\n\\n'; }\n\njson_field() { sed -nE 's/.*\"'\"$1\"'\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\\1/p' | head -n 1 | clean; }\n\nchromium_entry() {\n  json_objects | grep -E '\"name\"[[:space:]]*:[[:space:]]*\"chromium\"' | head -n 1\n}\n\nplist_version() {\n  local f=\"$1/Contents/Info.plist\" v=''\n  [ -r \"$f\" ] || return 0\n  if [ -x /usr/libexec/PlistBuddy ]; then\n    v=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \"$f\" 2&gt;/dev/null)\n  fi\n  if [ -z \"$v\" ]; then\n    v=$(tr -d '\\r\\n\\t' &lt;\"$f\" | sed -nE 's/.*CFBundleShortVersionString&lt;\\/key&gt;[[:space:]]*([^&lt;]*)&lt;\\/string&gt;.*/\\1/p')\n  fi\n  printf '%s' \"$v\"\n}\n\n# Pulls the version out of a browser binary without running it.\nbinary_version() {\n  tr -cs '0-9.' '\\n' 2&gt;/dev/null &lt;\"$1\" |\n    grep -E '^[1-9][0-9]{1,3}\\.0\\.[0-9]{4,5}\\.[0-9]{1,4}$' |\n    sort | uniq -c | sort -k1,1rn -k2,2n | awk 'NR==1 {print $2}'\n}\n\n# Playwright's revision number for a path, if it sits in a revision folder.\npath_revision() {\n  printf '%s\\n' \"$1\" | sed -nE 's#.*/(chromium|chromium_headless_shell)-([0-9]+)/.*#\\2#p' | head -n 1\n}\n\nrevision_version() {\n  awk -F \"$SEP\" -v r=\"$1\" '$1 == r &amp;&amp; $2 != \"\" {print $2; exit}' \"$T/revmap\"\n}\n\ntool_of() {\n  case $1 in\n    */ms-playwright/* | */.local-browsers/* | */chromium-[0-9]*/* | */chromium_*-[0-9]*/*) echo Playwright ;;\n    */puppeteer/* | */.local-chromium/*) echo Puppeteer ;;\n    */.agent-browser/*) echo agent-browser ;;\n    */selenium/*) echo Selenium ;;\n    /Applications/* | \"$HOME\"/Applications/* | /opt/google/* | /opt/microsoft/* | /usr/lib*/* | /snap/*) echo \"Installed app\" ;;\n    *) echo Other ;;\n  esac\n}\n\nshow_path() {\n  # shellcheck disable=SC2088  # a tilde for display, not for expansion\n  case $1 in\n    \"$HOME\"/*) printf '~/%s' \"${1:${#HOME}+1}\" ;;\n    *) printf '%s' \"$1\" ;;\n  esac | clean\n}\n\n# Shortens a Playwright binary path to its revision folder.\nbrowser_folder() {\n  printf '%s\\n' \"$1\" | sed -E 's#(/chromium[a-z_]*-[0-9]+)/.*#\\1#'\n}\n\n# Runs a command, killing it after $1 seconds. macOS has no timeout(1).\nrun_limited() {\n  local secs=$1 pid watcher rc\n  shift\n  \"$@\" &amp;\n  pid=$!\n  (sleep \"$secs\"; kill -9 \"$pid\" 2&gt;/dev/null) &gt;/dev/null 2&gt;&amp;1 &amp;\n  watcher=$!\n  wait \"$pid\"\n  rc=$?\n  kill \"$watcher\" 2&gt;/dev/null\n  wait \"$watcher\" 2&gt;/dev/null\n  return \"$rc\"\n}\n\nfetch() { curl -fsSL --max-time 15 \"$1\" 2&gt;/dev/null; }\n\n# Chromium version a Playwright release ships, read from its source tag.\nrelease_chromium() {\n  fetch \"$PW_RAW/v$1/packages/playwright-core/browsers.json\" | chromium_entry | json_field browserVersion\n}\n\n# ---- Reference versions -------------------------------------------------------\n\nBASELINE=$(baseline)\nREF_VERSION=$BASELINE REF_NOTE=\"built-in list from $BASELINE_DATE, may be out of date\"\nLATEST_PW='' LATEST_PW_CHROMIUM=''\n\nif [ -n \"$MIN_VERSION\" ]; then\n  is_version \"$MIN_VERSION\" || { echo \"--min-version must look like 156.0.8078.79\" &gt;&amp;2; exit 64; }\n  REF_VERSION=$MIN_VERSION REF_NOTE=\"your minimum, set with --min-version\"\nfi\n\nreference_failed() {\n  incomplete \"Could not reach Google for the current Chrome version, so this used the built-in list from $BASELINE_DATE.\"\n}\n\nif [ \"$OFFLINE\" = 0 ] &amp;&amp; command -v curl &gt;/dev/null 2&gt;&amp;1; then\n  if [ -z \"$MIN_VERSION\" ]; then\n    stable=$(fetch \"$CFT_URL\" | json_objects | grep -E '\"channel\"[[:space:]]*:[[:space:]]*\"Stable\"' | json_field version)\n    if ! is_version \"$stable\"; then\n      reference_failed\n    elif ver_lt \"$stable\" \"$REF_VERSION\"; then\n      REF_NOTE=\"built-in list; Google's feed still says $stable\"\n    else\n      REF_VERSION=$stable REF_NOTE=\"live from Google\"\n    fi\n  fi\n  LATEST_PW=$(fetch \"$NPM_URL\" | grep -oE '\"version\":\"[^\"]+\"' | head -n 1 | json_field version)\n  [ -z \"$LATEST_PW\" ] || LATEST_PW_CHROMIUM=$(release_chromium \"$LATEST_PW\")\nelif [ -z \"$MIN_VERSION\" ] &amp;&amp; [ \"$OFFLINE\" = 0 ]; then\n  reference_failed\nfi\n\n# ---- Where to search ----------------------------------------------------------\n\nadd_root() {\n  local r\n  for r in \"$@\"; do\n    [ -n \"$r\" ] &amp;&amp; [ -d \"$r\" ] &amp;&amp; ROOTS+=(\"$r\")\n  done\n}\n\n# Where tools keep browsers. Searched in every mode, because any of these can be\n# a link to another disk, or sit outside the home folder.\ncache_roots() {\n  add_root \"$HOME/Library/Caches/ms-playwright\" \"$HOME/.cache/ms-playwright\" \\\n    \"${XDG_CACHE_HOME:-}/ms-playwright\" \"${PLAYWRIGHT_BROWSERS_PATH:-}\" \\\n    \"$HOME/.cache/puppeteer\" \"${PUPPETEER_CACHE_DIR:-}\" \\\n    \"$HOME/.agent-browser\" \"$HOME/.cache/selenium\"\n  case $OS in\n    Darwin)\n      add_root /Applications/Google\\ Chrome*.app /Applications/Chromium*.app /Applications/Microsoft\\ Edge*.app \\\n        \"$HOME\"/Applications/Google\\ Chrome*.app \"$HOME\"/Applications/Chromium*.app\n      ;;\n    *)\n      add_root /opt/google /opt/microsoft /usr/lib/chromium /usr/lib/chromium-browser \\\n        /usr/lib64/chromium-browser /snap/chromium/current\n      ;;\n  esac\n  [ \"$IS_ROOT\" = 0 ] || add_root /Users/*/Library/Caches/ms-playwright /home/*/.cache/ms-playwright \\\n    /root/.cache/ms-playwright /var/root/Library/Caches/ms-playwright\n}\n\ncache_roots\ncase $MODE in\n  quick) ;;\n  all)\n    for d in /*; do\n      case $d in\n        /proc | /sys | /dev | /run | /System | /Volumes | /Network | /cores) ;;\n        /bin | /sbin | /boot | /lib | /lib32 | /lib64 | /libx32 | /lost+found) ;;\n        # On macOS these are links into /private, which is searched anyway.\n        /tmp | /var | /etc) [ \"$OS\" = Darwin ] || add_root \"$d\" ;;\n        *) add_root \"$d\" ;;\n      esac\n    done\n    ;;\n  *)\n    add_root \"$HOME\" \"${TMPDIR:-}\"\n    case $OS in\n      Darwin) add_root /Applications /Library /opt /usr/local /private/tmp /Users/Shared ;;\n      *) add_root /opt /usr/local /usr/lib /usr/lib64 /usr/share /srv /var/lib /var/www /var/opt /var/cache /tmp ;;\n    esac\n    [ \"$IS_ROOT\" = 0 ] || add_root /Users/* /home/* /root /var/root\n    ;;\nesac\nadd_root \"${EXTRA_ROOTS[@]}\"\n\n# With no caches at all, --quick has nothing to look at, which is a clean result.\n[ ${#ROOTS[@]} -gt 0 ] || ROOTS=(\"$T\")\n\n# Cloud folders can be slow and fetch files on demand, so only --all walks them.\nCLOUD=()\nif [ \"$MODE\" != all ]; then\n  CLOUD=(-o -path '*/Library/CloudStorage' -o -path '*/Library/Mobile Documents')\nfi\n\n# ---- Report header ------------------------------------------------------------\n\nHOST=$(hostname 2&gt;/dev/null)\nif [ \"$OS\" = Darwin ]; then\n  OS_LABEL=\"macOS $(sw_vers -productVersion 2&gt;/dev/null)\"\nelse\n  OS_LABEL=$OS\nfi\nprintf '%sPlaywright and Chromium audit%s  %s, %s %s, %s\\n' \"$C_BLD\" \"$C_OFF\" \"$HOST\" \"$OS_LABEL\" \"$(uname -m)\" \"$(date '+%Y-%m-%d %H:%M')\"\nprintf 'Checking against: %s (%s)\\n' \"$REF_VERSION\" \"$REF_NOTE\"\nLATEST_STATUS=''\nif [ -n \"$LATEST_PW_CHROMIUM\" ]; then\n  classify \"$LATEST_PW_CHROMIUM\"\n  LATEST_STATUS=$STATUS\n  printf 'Latest Playwright: %s, ships Chromium %s (%s)\\n' \"$LATEST_PW\" \"$LATEST_PW_CHROMIUM\" \"$LATEST_STATUS\"\nfi\nif [ \"$MODE\" = quick ]; then\n  note \"Searching browser caches only (--quick)...\"\nelse\n  note \"Searching ${#ROOTS[@]} locations. This can take a few minutes on a big disk...\"\n  [ \"$OS\" != Darwin ] || note \"If macOS asks for folder access, allow it so those folders get searched.\"\nfi\n\n# ---- Search -------------------------------------------------------------------\n\nODD_PATHS=0\nwhile IFS= read -r -d '' p; do\n  case $p in\n    *$'\\n'*) ODD_PATHS=$((ODD_PATHS + 1)) ;;\n    *.app) printf '%s\\n' \"$p\" &gt;&gt;\"$T/apps\" ;;\n    */browsers.json) printf '%s\\n' \"${p%/*}\" &gt;&gt;\"$T/pkgdirs\" ;;\n    *.jar | *.zip) printf '%s\\n' \"$p\" &gt;&gt;\"$T/archives\" ;;\n    *) [ ! -x \"$p\" ] || printf '%s\\n' \"$p\" &gt;&gt;\"$T/bins\" ;;\n  esac\ndone &lt; &lt;(find -H \"${ROOTS[@]}\" \\\n  \\( -type d \\( -name .git -o -name .Trash -o -name '.Trash-*' -o -name '*.photoslibrary' \\\n  -o -name .Spotlight-V100 -o -name .fseventsd -o -name .DocumentRevisions-V100 \\\n  -o -path '*/Library/Mail' -o -path '*/Library/Messages' -o -path '*/Library/Safari' \\\n  -o -path '*/Library/Containers/com.docker.docker' -o -path /private/var/vm \"${CLOUD[@]}\" \\) -prune \\) -o \\\n  \\( -type d \\( -name 'Google Chrome*.app' -o -name 'Chromium*.app' -o -name 'Microsoft Edge*.app' \\) -print0 -prune \\) -o \\\n  \\( -type f \\( -name browsers.json -o -name chrome -o -name chrome-headless-shell -o -name headless_shell \\\n  -o -name chromium -o -name chromium-browser -o -name msedge \\\n  -o -name 'driver-bundle-*.jar' -o -name 'playwright-core-npm-*.zip' \\) -print0 \\) \\\n  2&gt;\"$T/find.err\"; echo \"$?\" &gt;\"$T/find.rc\")\n\n[ \"$ODD_PATHS\" -eq 0 ] || incomplete \"$ODD_PATHS path(s) with a line break in the name were skipped.\"\n\n# Each Playwright browser cache lists the installs that use it in .links/.\n{\n  sed -nE 's#^(.*)/chromium[a-z_]*-[0-9]+/.*#\\1#p' \"$T/apps\" \"$T/bins\"\n  printf '%s\\n' \"$HOME/Library/Caches/ms-playwright\" \"$HOME/.cache/ms-playwright\"\n  [ -z \"${PLAYWRIGHT_BROWSERS_PATH:-}\" ] || printf '%s\\n' \"$PLAYWRIGHT_BROWSERS_PATH\"\n} | sort -u &gt;\"$T/caches\"\nwhile IFS= read -r cache; do\n  for link in \"$cache\"/.links/*; do\n    [ -f \"$link\" ] || continue\n    target=$(head -n 1 \"$link\" 2&gt;/dev/null)\n    [ ! -f \"$target/browsers.json\" ] || printf '%s\\n' \"$target\" &gt;&gt;\"$T/pkgdirs\"\n  done\ndone &lt;\"$T/caches\"\n\n# ---- Playwright packages ------------------------------------------------------\n\nadd_package() { # path, package name, version, browsers.json text\n  local shown=$1 name=$2 version=$3 entry rev bver\n  entry=$(printf '%s' \"$4\" | chromium_entry)\n  rev=$(printf '%s' \"$entry\" | json_field revision)\n  [ -n \"$rev\" ] || return 0\n  bver=$(printf '%s' \"$entry\" | json_field browserVersion)\n  printf '%s%s%s\\n' \"$rev\" \"$SEP\" \"$bver\" &gt;&gt;\"$T/revmap\"\n  printf '%s%s%s%s%s%s%s%s%s\\n' \"$shown\" \"$SEP\" \"$name\" \"$SEP\" \"$version\" \"$SEP\" \"$rev\" \"$SEP\" \"$bver\" &gt;&gt;\"$T/pkg_raw\"\n}\n\n: &gt;\"$T/pkg_raw\"\nsort -u \"$T/pkgdirs\" | while IFS= read -r dir; do\n  json=$(cat \"$dir/browsers.json\" 2&gt;/dev/null)\n  pkg=$(head -n 40 \"$dir/package.json\" 2&gt;/dev/null)\n  add_package \"$dir\" \"$(printf '%s\\n' \"$pkg\" | json_field name)\" \"$(printf '%s\\n' \"$pkg\" | json_field version)\" \"$json\"\ndone\n\n# Yarn PnP zips and Java driver jars keep the same files inside an archive.\nif command -v unzip &gt;/dev/null 2&gt;&amp;1; then\n  sort -u \"$T/archives\" | while IFS= read -r file; do\n    json=$(unzip -p \"$file\" '*/browsers.json' 2&gt;/dev/null)\n    version=$(printf '%s\\n' \"${file##*/}\" | sed -nE 's/^(playwright-core-npm|driver-bundle)-([0-9][0-9A-Za-z.]*[0-9])[-.].*/\\2/p')\n    add_package \"$file\" playwright-core \"$version\" \"$json\"\n  done\nfi\n\n# ---- Browsers on disk ---------------------------------------------------------\n\nadd_browser() { # status, version, tool, shown path, why\n  printf '%s%s%s%s%s%s%s%s%s%s%s\\n' \"$(rank \"$1\")\" \"$SEP\" \"$1\" \"$SEP\" \"$2\" \"$SEP\" \"$3\" \"$SEP\" \"$4\" \"$SEP\" \"$5\" &gt;&gt;\"$T/browsers\"\n}\n\nrecord_browser() { # path, version\n  local path=$1 version=$2 rev tool\n  is_version \"$version\" || version=''\n  rev=$(path_revision \"$path/\")\n  if [ -z \"$version\" ] &amp;&amp; [ -n \"$rev\" ]; then\n    version=$(revision_version \"$rev\")\n    is_version \"$version\" || version=''\n  fi\n  [ -z \"$rev\" ] || printf '%s%s%s\\n' \"$rev\" \"$SEP\" \"$version\" &gt;&gt;\"$T/disk_revs\"\n  tool=$(tool_of \"$path/\")\n  case ${path##*/} in\n    Microsoft\\ Edge*.app | msedge)\n      # Edge has its own build numbers, but its major version tracks Chromium's.\n      if is_version \"$version\" &amp;&amp; [ \"$(major \"$version\")\" -lt \"$(major \"$REF_VERSION\")\" ]; then\n        add_browser OUTDATED \"$version\" \"Microsoft Edge\" \"$(show_path \"$path\")\" \"behind the current Chromium major version\"\n      else\n        add_browser INFO \"${version:-?}\" \"Microsoft Edge\" \"$(show_path \"$path\")\" \"Edge numbers its builds differently, so keep it updated from its About page\"\n      fi\n      return\n      ;;\n  esac\n  classify \"$version\"\n  # Beta and Canary update themselves, unlike a build frozen in a tool's cache.\n  if [ \"$STATUS\" = PRERELEASE ] &amp;&amp; [ \"$tool\" = \"Installed app\" ]; then\n    STATUS=OK WHY=\"pre-release channel that updates itself\"\n  fi\n  add_browser \"$STATUS\" \"${version:-?}\" \"$tool\" \"$(show_path \"$(browser_folder \"$path\")\")\" \"$WHY\"\n}\n\nsort -u \"$T/apps\" | while IFS= read -r app; do\n  record_browser \"$app\" \"$(plist_version \"$app\")\"\ndone\nsort -u \"$T/bins\" | while IFS= read -r bin; do\n  version=$(binary_version \"$bin\")\n  # A small file with no version is a wrapper script, not a browser.\n  if [ -z \"$version\" ] &amp;&amp; [ -r \"$bin\" ] &amp;&amp; [ -z \"$(path_revision \"$bin/\")\" ] &amp;&amp; [ \"$(wc -c &lt;\"$bin\")\" -lt 1000000 ]; then\n    continue\n  fi\n  record_browser \"$bin\" \"$version\"\ndone\nsort -u \"$T/browsers\" -o \"$T/browsers\"\n\n# ---- Playwright installs ------------------------------------------------------\n\n# The oldest build in a revision's folders. Someone may have swapped in a newer\n# one by hand, so what is on disk beats what the package pins.\nlowest_on_disk() {\n  local r v low=''\n  while IFS=\"$SEP\" read -r r v; do\n    [ \"$r\" = \"$1\" ] &amp;&amp; [ -n \"$v\" ] || continue\n    if [ -z \"$low\" ] || ver_lt \"$v\" \"$low\"; then low=$v; fi\n  done &lt;\"$T/disk_revs\"\n  printf '%s' \"$low\"\n}\n\nsort -u \"$T/pkg_raw\" | while IFS=\"$SEP\" read -r shown name version rev bver; do\n  actual=$(lowest_on_disk \"$rev\")\n  actual=${actual:-$bver}\n  if [ -n \"$actual\" ]; then\n    classify \"$actual\"\n  else\n    STATUS=VULNERABLE WHY=\"too old to report its Chromium version\"\n  fi\n  label=\"Chromium ${actual:-rev $rev}\"\n  case $STATUS in\n    VULNERABLE | OUTDATED | PRERELEASE)\n      [ -n \"$actual\" ] &amp;&amp; WHY=''\n      grep -q \"^$rev$SEP\" \"$T/disk_revs\" || STATUS=\"NOT DOWNLOADED\" WHY=''\n      ;;\n  esac\n  version=${version:-?}\n  case $version in\n    *-alpha-*) version=\"${version%%-alpha-*}-alpha\" ;;\n    *-beta-*) version=\"${version%%-beta-*}-beta\" ;;\n  esac\n  case $name in\n    playwright-core | '') ;;\n    *) version=\"$version ($name)\" ;;\n  esac\n  printf '%s%s%s%s%s%s%s%s%s%s%s\\n' \"$(rank \"$STATUS\")\" \"$SEP\" \"$STATUS\" \"$SEP\" \"$version\" \"$SEP\" \"$label\" \"$SEP\" \"$(show_path \"$shown\")\" \"$SEP\" \"$WHY\" &gt;&gt;\"$T/packages\"\ndone\nsort -u \"$T/packages\" -o \"$T/packages\"\n\n# ---- Docker images ------------------------------------------------------------\n\nadd_image() { # status, version, image, why\n  printf '%s%s%s%s%s%s%s%s%s\\n' \"$(rank \"$1\")\" \"$SEP\" \"$1\" \"$SEP\" \"$2\" \"$SEP\" \"$3\" \"$SEP\" \"$4\" &gt;&gt;\"$T/images\"\n}\n\nif command -v docker &gt;/dev/null 2&gt;&amp;1; then\n  if run_limited 10 docker image ls --format '{{.Repository}}:{{.Tag}}' &gt;\"$T/image_list\" 2&gt;/dev/null; then\n    grep -i playwright \"$T/image_list\" | clean | sort -u &gt;\"$T/image_names\"\n    while IFS= read -r image; do\n      pw=$(printf '%s\\n' \"${image##*:}\" | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -n 1)\n      bver=''\n      if [ -n \"$pw\" ] &amp;&amp; [ \"$OFFLINE\" = 0 ]; then bver=$(release_chromium \"$pw\"); fi\n      if [ -n \"$bver\" ]; then\n        classify \"$bver\"\n        add_image \"$STATUS\" \"$bver\" \"$image\" \"$WHY\"\n      else\n        add_image UNKNOWN \"?\" \"$image\" \"cannot tell its Chromium from the tag; check inside the image\"\n      fi\n    done &lt;\"$T/image_names\"\n    sort -u \"$T/images\" -o \"$T/images\"\n  else\n    limit \"Could not list Docker images. Start Docker and run this again to check them.\"\n  fi\nfi\n\n# ---- Running processes --------------------------------------------------------\n\nps axww -o command= 2&gt;/dev/null &gt;\"$T/ps\"\n# Only lines that start with a browser program, so a command that merely\n# mentions one (an editor, a grep) is not counted. Helpers carry --type=.\ngrep -E '^/[^ ]*/(chrome-headless-shell[^ ]*|headless_shell)( |$)|^/[^ ]*(ms-playwright|local-browsers|pw-browsers|puppeteer|agent-browser)[^ ]*/chrome( |$)|^/.*(Google Chrome for Testing\\.app/Contents/MacOS/Google Chrome for Testing|Chromium\\.app/Contents/MacOS/Chromium)( |$)' \"$T/ps\" |\n  grep -v -e '--type=' &gt;\"$T/running\"\nRUNNING=$(awk 'END {print NR}' \"$T/running\")\nNO_SANDBOX=$(grep -c -e '--no-sandbox' \"$T/running\")\nOLD_CHROME=''\nif [ \"$OS\" = Darwin ]; then\n  for v in $(grep -oE 'Google Chrome Framework\\.framework/Versions/[0-9.]+' \"$T/ps\" | sed 's#.*/##' | sort -u); do\n    classify \"$v\"\n    case $STATUS in\n      VULNERABLE | OUTDATED) OLD_CHROME=\"$OLD_CHROME${OLD_CHROME:+, }$v\" ;;\n    esac\n  done\nfi\n\n# ---- What the search could not cover ------------------------------------------\n\ngrep -vE 'Permission denied|Operation not permitted|No such file or directory' \"$T/find.err\" | clean &gt;\"$T/find.other\"\nFIND_RC=$(cat \"$T/find.rc\" 2&gt;/dev/null)\nif [ -z \"$FIND_RC\" ] || [ \"$FIND_RC\" -gt 1 ] || [ -s \"$T/find.other\" ]; then\n  reason=$(head -n 1 \"$T/find.other\")\n  incomplete \"The search did not finish cleanly (${reason:-find stopped with code ${FIND_RC:-unknown}}).\"\nfi\n\n# A refused folder only counts against a clean result if an install could be in\n# it. macOS refuses its own app data (Calendar, Contacts and so on) without Full\n# Disk Access, and other people's folders are refused without sudo.\ndenied_matters() {\n  local r rest\n  case $1 in\n    */ms-playwright* | */node_modules* | */site-packages* | */pnpm* | */puppeteer* | */.local-browsers*) return 0 ;;\n  esac\n  if [ \"$OS\" = Darwin ]; then\n    # Matches /Library/... and /Users/someone/Library/..., not any folder called Library.\n    rest=${1#/Users/}\n    rest=${rest#*/}\n    case $rest in Library/*) return 1 ;; esac\n    case $1 in \"$HOME\"/Library/* | /private/*) return 1 ;; esac\n  fi\n  [ \"$IS_ROOT\" = 0 ] || return 0\n  case $1 in\n    \"$HOME\" | \"$HOME\"/*) return 0 ;;\n  esac\n  for r in \"${EXTRA_ROOTS[@]}\"; do\n    case $1 in\n      \"$r\" | \"$r\"/*) return 0 ;;\n    esac\n  done\n  return 1\n}\n\n: &gt;\"$T/denied_main\"; : &gt;\"$T/denied_other\"\ngrep -E 'Permission denied|Operation not permitted' \"$T/find.err\" | clean |\n  sed -E \"s/^[^:]*: *//; s/: (Permission denied|Operation not permitted)$//; s/^'//; s/'$//\" |\n  while IFS= read -r dir; do\n    if denied_matters \"$dir\"; then\n      show_path \"$dir\" &gt;&gt;\"$T/denied_main\"\n      echo &gt;&gt;\"$T/denied_main\"\n    else\n      echo \"$dir\" &gt;&gt;\"$T/denied_other\"\n    fi\n  done\nN_DENIED=$(awk 'END {print NR}' \"$T/denied_main\")\nN_OTHER=$(awk 'END {print NR}' \"$T/denied_other\")\nif [ \"$N_DENIED\" -gt 0 ]; then\n  incomplete \"$N_DENIED folder(s) could not be read, for example:\"\n  head -n 3 \"$T/denied_main\" | sed 's/^/      /' &gt;&gt;\"$T/limits\"\n  if [ \"$OS\" = Darwin ]; then\n    limit \"To cover them, allow the folder-access prompts or give your terminal Full Disk Access\"\n    limit \"in System Settings, then run this again.\"\n  else\n    limit \"To cover them, run this again with sudo.\"\n  fi\nfi\n[ \"$N_OTHER\" -eq 0 ] || limit \"$N_OTHER private system or other-user folder(s) could not be read. An install there is unlikely.\"\n\nif [ \"$MODE\" = default ] &amp;&amp; { [ -d \"$HOME/Library/CloudStorage\" ] || [ -d \"$HOME/Library/Mobile Documents\" ]; }; then\n  limit \"Cloud-synced folders in ~/Library were skipped. Name one as a folder to search it, or use --all.\"\nfi\n[ \"$MODE\" != quick ] || limit \"--quick only looked in browser caches. Run without it to search all projects.\"\n\n# ---- Report -------------------------------------------------------------------\n\npaint() {\n  case $1 in\n    VULNERABLE) printf '%s' \"$C_RED\" ;;\n    OUTDATED | UNKNOWN | PRERELEASE | \"NOT DOWNLOADED\") printf '%s' \"$C_YEL\" ;;\n    OK) printf '%s' \"$C_GRN\" ;;\n  esac\n}\n\nheading \"BROWSERS ON DISK\"\n[ -s \"$T/browsers\" ] || echo \"  None found.\"\nwhile IFS=\"$SEP\" read -r r s ver tool shown why; do\n  if [ \"$QUIET\" = 1 ] &amp;&amp; [ \"$r\" -gt 4 ]; then continue; fi\n  printf '  %s%-14s%s %-15s %-14s %s\\n' \"$(paint \"$s\")\" \"$s\" \"$C_OFF\" \"$ver\" \"$tool\" \"$shown\"\n  if [ -n \"$why\" ]; then printf '  %s%14s %s%s\\n' \"$C_DIM\" \"\" \"$why\" \"$C_OFF\"; fi\ndone &lt;\"$T/browsers\"\n\nif [ -s \"$T/packages\" ]; then\n  heading \"PLAYWRIGHT INSTALLS\"\n  while IFS=\"$SEP\" read -r r s ver label shown why; do\n    if [ \"$QUIET\" = 1 ] &amp;&amp; [ \"$r\" -gt 4 ]; then continue; fi\n    printf '  %s%-14s%s %-15s %-23s %s\\n' \"$(paint \"$s\")\" \"$s\" \"$C_OFF\" \"$ver\" \"$label\" \"$shown\"\n    if [ -n \"$why\" ]; then printf '  %s%14s %s%s\\n' \"$C_DIM\" \"\" \"$why\" \"$C_OFF\"; fi\n  done &lt;\"$T/packages\"\n  if grep -q \"${SEP}NOT DOWNLOADED$SEP\" \"$T/packages\"; then\n    printf '  %sNOT DOWNLOADED: pins a build that is unpatched or unconfirmed, but that build was%s\\n' \"$C_DIM\" \"$C_OFF\"\n    printf '  %snot found in the folders searched. Its next \"playwright install\" would fetch it.%s\\n' \"$C_DIM\" \"$C_OFF\"\n  fi\nfi\n\nif [ -s \"$T/images\" ]; then\n  heading \"DOCKER IMAGES\"\n  while IFS=\"$SEP\" read -r r s ver image why; do\n    if [ \"$QUIET\" = 1 ] &amp;&amp; [ \"$r\" -gt 4 ]; then continue; fi\n    printf '  %s%-14s%s %-15s %s\\n' \"$(paint \"$s\")\" \"$s\" \"$C_OFF\" \"$ver\" \"$image\"\n    if [ -n \"$why\" ]; then printf '  %s%14s %s%s\\n' \"$C_DIM\" \"\" \"$why\" \"$C_OFF\"; fi\n  done &lt;\"$T/images\"\nfi\n\nif [ \"$RUNNING\" -gt 0 ] || [ -n \"$OLD_CHROME\" ]; then\n  heading \"RUNNING NOW\"\n  [ \"$RUNNING\" -eq 0 ] || printf '  %s automation browser(s) running, %s with --no-sandbox. Running browsers keep their old code until restarted.\\n' \"$RUNNING\" \"$NO_SANDBOX\"\n  [ -z \"$OLD_CHROME\" ] || printf '  %sGoogle Chrome is still running %s. Relaunch it to finish updating.%s\\n' \"$C_YEL\" \"$OLD_CHROME\" \"$C_OFF\"\nfi\n\nif [ -s \"$T/limits\" ]; then\n  heading \"LIMITS OF THIS RUN\"\n  cat \"$T/limits\"\nfi\n\ncount_status() { cut -d \"$SEP\" -f 2 \"$T/browsers\" \"$T/images\" 2&gt;/dev/null | grep -cx \"$1\"; }\nN_VULN=$(count_status VULNERABLE)\nN_OUT=$(count_status OUTDATED)\nN_PRE=$(count_status PRERELEASE)\nN_UNKNOWN=$(cut -d \"$SEP\" -f 2 \"$T/browsers\" \"$T/images\" \"$T/packages\" 2&gt;/dev/null | grep -cx UNKNOWN)\n\nheading \"RESULT\"\nif [ \"$N_VULN\" -gt 0 ] || [ \"$N_OUT\" -gt 0 ] || [ -n \"$OLD_CHROME\" ]; then\n  EXIT=1\n  printf '  %sFound %s vulnerable and %s outdated browsers.%s\\n' \"$C_RED\" \"$N_VULN\" \"$N_OUT\" \"$C_OFF\"\n  [ -z \"$OLD_CHROME\" ] || printf '  %sGoogle Chrome needs a relaunch.%s\\n' \"$C_RED\" \"$C_OFF\"\nelif [ \"$N_UNKNOWN\" -gt 0 ] || [ \"$N_PRE\" -gt 0 ] || [ \"$INCOMPLETE\" = 1 ]; then\n  EXIT=2\n  printf '  %sNothing known to be vulnerable, but this run could not confirm everything (see above).%s\\n' \"$C_YEL\" \"$C_OFF\"\nelse\n  EXIT=0\n  printf '  %sNo vulnerable or outdated browser found.%s\\n' \"$C_GRN\" \"$C_OFF\"\nfi\n\n# True if a browser row for any of these tools is vulnerable or outdated.\nflagged() {\n  local t\n  for t in \"$@\"; do\n    awk -F \"$SEP\" -v t=\"$t\" '($2 == \"VULNERABLE\" || $2 == \"OUTDATED\") &amp;&amp; $4 == t {found = 1} END {exit !found}' \"$T/browsers\" &amp;&amp; return 0\n  done\n  return 1\n}\n\nif [ \"$EXIT\" = 1 ]; then\n  heading \"WHAT TO DO\"\n  if [ -n \"$OLD_CHROME\" ] || flagged \"Installed app\" \"Microsoft Edge\"; then\n    echo \"  Update the installed browsers listed above from their About pages, then relaunch them.\"\n  fi\n  if flagged Playwright || grep -qE \"^[0-9]+$SEP(VULNERABLE|OUTDATED)$SEP\" \"$T/images\"; then\n    if [ \"$LATEST_STATUS\" = OK ] || [ \"$LATEST_STATUS\" = PRERELEASE ]; then\n      cat &lt;&lt;EOF\n  Upgrade Playwright to $LATEST_PW, then fetch its browser:\n    npm i -D @playwright/test@latest &amp;&amp; npx playwright install chromium\n    pip install -U playwright &amp;&amp; playwright install chromium    (Python)\nEOF\n    else\n      if [ -n \"$LATEST_PW_CHROMIUM\" ]; then\n        printf '  No stable Playwright ships a fully patched Chromium yet (%s has %s).\\n' \"$LATEST_PW\" \"$LATEST_PW_CHROMIUM\"\n        gaps=$(missing_fixes \"$LATEST_PW_CHROMIUM\")\n        case $gaps in\n          *exploited*) printf '  Still move older installs to %s.\\n' \"$LATEST_PW\" ;;\n          *) printf '  Still move older installs to %s, which closes the older exploited bugs.\\n' \"$LATEST_PW\" ;;\n        esac\n      else\n        echo \"  Upgrade Playwright, then check whether its Chromium is patched yet.\"\n      fi\n      cat &lt;&lt;'EOF'\n  Until a patched release ships, use your up-to-date Chrome for anything that\n  touches the open web, and keep the sandbox on:\n    chromium.launch({ channel: 'chrome', chromiumSandbox: true })\n    use: { channel: 'chrome', launchOptions: { chromiumSandbox: true } }    (Playwright Test)\n  Keep the bundled browser to pages you trust, such as your own localhost.\nEOF\n    fi\n    echo \"  Playwright passes --no-sandbox unless chromiumSandbox is true, so set it either way.\"\n  fi\n  if flagged Puppeteer agent-browser Selenium Other; then\n    echo \"  For Puppeteer, agent-browser and other tools listed above, update the tool itself.\"\n  fi\n  echo \"  Then delete browser folders nothing needs, restart running sessions, and run this again.\"\nfi\n\nexit \"$EXIT\"\n", "url": "https://gist.github.com/mrcointreau/f5d7de316059888275a16060ce02157a"}, {"timestamp": "2026-11-03T21:17:48.258Z", "date_only": false, "value": "2026-11-03T21:17:48.258Z", "kind": "other", "label": "Updated", "source": "cvelistv5", "record": "CVE-2026-76024", "field": "cveMetadata.dateUpdated"}, {"timestamp": "2026-11-03T21:17:48.258Z", "date_only": false, "value": "2026-11-03T21:17:48.258Z", "kind": "other", "label": "Updated", "source": "cvelistv5 / Chrome", "record": "CVE-2026-76024", "field": "containers.cna.providerMetadata.dateUpdated"}, {"timestamp": "2026-11-03T21:53:29Z", "date_only": false, "value": "2026-11-03T21:53:29Z", "kind": "other", "label": "Advisory revision", "source": "csaf_opensuse \u00b7 OPENSUSE-SU-2026:12377-1", "record": "OPENSUSE-SU-2026:12377-1", "field": "document.tracking.revision_history.1.date", "linked_record": true}, {"timestamp": "2026-11-03T21:53:29Z", "date_only": false, "value": "2026-11-03T21:53:29Z", "kind": "other", "label": "Updated", "source": "csaf_opensuse \u00b7 OPENSUSE-SU-2026:12377-1", "record": "OPENSUSE-SU-2026:12377-1", "field": "document.tracking.current_release_date", "linked_record": true}, {"timestamp": "2026-11-03T22:19:48.247Z", "date_only": false, "value": "2026-11-03T22:19:48.247", "kind": "other", "label": "Updated", "source": "fkie_nvd \u00b7 FKIE_CVE-2026-76024", "record": "FKIE_CVE-2026-76024", "field": "lastModified", "linked_record": true}], "metrics": {"as_of": "2026-11-04T08:24:36.527Z", "anchors": {"origin": {"timestamp": "2026-10-30T00:00:00Z", "date_only": false, "value": "2026-10-30T00:00:00", "kind": "milestone", "label": "Published", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "initial_release_date", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, "discovery": null, "report": null, "acknowledgement": null, "disclosure": {"timestamp": "2026-10-30T00:00:00Z", "date_only": false, "value": "2026-10-30T00:00:00", "kind": "milestone", "label": "Published", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "initial_release_date", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, "exploit_publication": null, "exploitation": null, "patch": null}, "vendor_response_time": {"available": false, "missing": ["start", "end"]}, "time_to_exploit": {"available": false, "missing": ["end"]}, "disclosure_to_exploit": {"available": false, "missing": ["end"]}, "disclosure_to_exploit_publication": {"available": false, "missing": ["end"]}, "time_to_patch": {"available": false, "missing": ["start", "end"]}, "total_time_to_patch": {"available": false, "missing": ["start", "end"]}, "disclosure_to_patch": {"available": false, "missing": ["end"]}, "time_unpatched": {"available": true, "seconds": 462276.52771, "hours": 128.4101465861111, "days": 5.350422774421296, "min_seconds": 375876.52771, "max_seconds": 462276.52771, "approximate": true, "ongoing": true, "order_known": true, "batch_listing": false, "end_before_start": false, "start": {"timestamp": "2026-10-30T00:00:00Z", "date_only": false, "value": "2026-10-30T00:00:00", "kind": "milestone", "label": "Published", "source": "certfr_avis \u00b7 CERTFR-2026-AVI-1422", "record": "CERTFR-2026-AVI-1422", "field": "initial_release_date", "metric_role": "disclosure", "metric_basis": "recorded_event", "linked_record": true}, "end": {"timestamp": "2026-11-04T08:24:36.527Z", "date_only": false, "label": "As of", "source": "Calculation clock"}}, "exploitation_to_patch": {"available": false, "missing": ["start"]}, "exploit_publication_to_patch": {"available": false, "missing": ["start"]}}}
