

Enterprise‑focused guide for integrating Chrome‑security checks into your applications
Why this matters – Chrome 148 shipped a massive security update that fixed 151 vulnerabilities (including several zero‑days). Enterprises that enforce the minimum Chrome version (or validate URLs with the latest Safe Browsing definitions) dramatically reduce their attack surface. The code below shows how to programmatically verify that a client is running Chrome ≥ 148 and how to query Google’s Safe Browsing API (the service that powers Chrome’s built‑in protection) to see if a URL is known malicious.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | How to obtain |
|---|---|---|
| Google Cloud project | Hosts the Safe Browsing API and lets you create an API key. | Go to https://console.cloud.google.com/ → Create Project (or use an existing one). |
| Safe Browsing API enabled | Required to call threatMatches:find. | In Cloud Console → APIs & Services → Library → search “Safe Browsing API” → Enable. |
| API key | Authenticates your requests. | APIs & Services → Credentials → Create credentials → API key. Restrict to your IP range or application for production. |
| Chrome ≥ 148 (optional for version‑check) | Demonstrates the enterprise policy enforcement. | Any recent Chrome/Chromium build; you can verify the version via CLI. |
| Python 3.9+ or Node.js 18+ | Runtime for the code samples. | https://www.python.org/downloads/ • https://nodejs.org/ |
| Internet access | To reach safebrowsing.googleapis.com. | – |
Tip: If you only need the version‑check (no API calls), you can skip the Cloud project and API key steps.
<a name="step-2-installation-and-setup"></a>
# 1️⃣ Create a virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
# 2️⃣ Install the HTTP client
pip install --upgrade pip
pip install requests tenacity # tenacity for retry/backoff
# 1️⃣ Initialise a new project
npm init -y
npm install typescript ts-node @types/node --save-dev # dev deps
npm install axios tenacity # axios for HTTP, tenacity for retry logic
# 2️⃣ Create a basic tsconfig.json (if you don’t have one)
npx tsc --init --rootDir . --outDir dist --esModuleInterop true --module commonjs --target es2022
Note:
tenacityis a Python‑only library; for Node we’ll use a lightweight retry helper (p-retry). If you prefer not to add extra deps, you can implement a simple loop withsetTimeout.
<a name="step-3-basic-implementation"></a>
Below are two self‑contained examples:
Both snippets include:
tenacity/p-retry)logging module or console)# file: chrome_security_check.py
"""
Chrome 148 version verification + Safe Browsing lookup.
Requirements:
pip install requests tenacity
Environment:
SAFE_BROWSING_API_KEY – your Google Cloud API key (optional for version‑check only)
"""
import json
import logging
import platform
import subprocess
import sys
from typing import List, Dict, Any
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
# ----------------------------------------------------------------------
# Configuration
# ----------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)
log = logging.getLogger("chrome_security")
SAFE_BROWSING_ENDPOINT = (
"https://safebrowsing.googleapis.com/v4/threatMatches:find"
)
MIN_CHROME_VERSION = 148 # Chrome 148 is the baseline after the 151‑vulnerability patch
# ----------------------------------------------------------------------
# Helper: Chrome version detection (cross‑platform)
# ----------------------------------------------------------------------
def get_installed_chrome_version() -> int:
"""
Returns the major version number of the Chrome/Chromium binary found in PATH.
Raises RuntimeError if Chrome cannot be located.
"""
system = platform.system()
try:
if system == "Windows":
# Look for chrome.exe in typical install locations via `where`
chrome_path = subprocess.check_output(
["where", "chrome"], text=True
).strip().splitlines()[0]
output = subprocess.check_output(
[chrome_path, "--version"], text=True, stderr=subprocess.STDOUT
)
elif system == "Darwin": # macOS
output = subprocess.check_output(
["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"],
text=True,
stderr=subprocess.STDOUT,
)
else: # Linux and others
output = subprocess.check_output(
["google-chrome", "--version"], text=True, stderr=subprocess.STDOUT
)
except (subprocess.CalledProcessError, FileNotFoundError) as exc:
raise RuntimeError("Chrome executable not found or failed to run") from exc
# Example output: "Google Chrome 148.0.7504.0"
version_str = output.strip().split()[-1]
major = int(version_str.split(".")[0])
return major
# ----------------------------------------------------------------------
# Safe Browsing API call (with retry/backoff)
# ----------------------------------------------------------------------
@retry(
reraise=True,
stop=stop_after_attempt(4),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((requests.RequestException,)),
)
def check_url_safe_browsing(url: str, api_key: str) -> Dict[str, Any]:
"""
Sends a URL to Google Safe Browsing v4 and returns the parsed JSON response.
On success, the dict contains a 'matches' key if the URL is deemed unsafe.
"""
payload = {
"client": {
"clientId": "icarax-techblog-demo",
"clientVersion": "1.0.0",
},
"threatInfo": {
"threatTypes": [
"MALWARE",
"SOCIAL_ENGINEERING",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION",
],
"platformTypes": ["ANY_PLATFORM"],
"threatEntryTypes": ["URL"],
"threatEntries": [{"url": url}],
},
}
params = {"key": api_key}
headers = {"Content-Type": "application/json"}
log.info("Querying Safe Browsing for %s", url)
resp = requests.post(
SAFE_BROWSING_ENDPOINT,
params=params,
json=payload,
headers=headers,
timeout=10,
)
# Raise for HTTP errors (4xx/5xx) – tenacity will retry on these
resp.raise_for_status()
return resp.json()
# ----------------------------------------------------------------------
# Main orchestration
# ----------------------------------------------------------------------
def main(urls: List[str]) -> None:
# 1️⃣ Verify Chrome version
try:
chrome_version = get_installed_chrome_version()
log.info("Detected Chrome version: %s", chrome_version)
if chrome_version < MIN_CHROME_VERSION:
log.error(
"Chrome version %s is below the required minimum %s. "
"Please update Chrome immediately.",
chrome_version,
MIN_CHROME_VERSION,
)
sys.exit(1)
else:
log.info("Chrome version satisfies the enterprise baseline (≥%s).", MIN_CHROME_VERSION)
except RuntimeError as e:
log.warning("Could not determine Chrome version: %s", e)
log.warning("Proceeding with Safe Browsing checks only.")
# 2️⃣ Safe Browsing checks (skip if no API key)
api_key = None # In real code: os.getenv("SAFE_BROWSING_API_KEY")
try:
import os
api_key = os.getenv("SAFE_BROWSING_API_KEY")
if not api_key:
log.warning("SAFE_BROWSING_API_KEY not set – skipping URL safety checks.")
except Exception: # pragma: no cover
pass
if api_key:
for url in urls:
try:
result = check_url_safe_browsing(url, api_key)
matches = result.get("matches", [])
if matches:
log.error("⚠️ URL %s is UNSAFE: %s", url, json.dumps(matches, indent=2))
else:
log.info("✅ URL %s is clean (no threat matches).", url)
except requests.HTTPError as http_err:
# Handle specific status codes
if http_err.response.status_code == 400:
log.error("Bad request (400) for %s – check payload format.", url)
elif http_err.response.status_code == 403:
log.error(
"Forbidden (403) – likely invalid or missing API key for %s.", url
)
elif http_err.response.status_code == 429:
log.error("Rate limit exceeded (429) for %s – back off.", url)
else:
log.exception("HTTP error while checking %s", url)
except Exception as exc: # pragma: no cover
log.exception("Unexpected error while checking %s", url)
else:
log.info("No API key provided – only Chrome version check performed.")
# ----------------------------------------------------------------------
# Entry point
# ----------------------------------------------------------------------
if __name__ == "__main__":
# Example URLs – replace with your own list or read from stdin/file
SAMPLE_URLS = [
"https://www.google.com",
"http://malwaredomain.com", # known test malicious URL (may be blocked)
]
main(SAMPLE_URLS)
export SAFE_BROWSING_API_KEY="YOUR_API_KEY_HERE"
python chrome_security_check.py
If you only need the version check, omit the
SAFE_BROWSING_API_KEYline – the script will still exit with an error if Chrome < 148.
// file: chrome-security-check.ts
/*
Chrome 148 version verification + Safe Browsing lookup (Node.js).
Prerequisites:
npm install axios p-retry
Environment:
SAFE_BROWSING_API_KEY – Google Cloud API key (optional for version‑check only)
*/
import { execSync } from "child_process";
import axios, { AxiosError } from "axios";
import { retry } from "p-retry";
import * as dotenv from "dotenv";
dotenv.config(); // loads .env into process.env
const LOG = console; // simple logger; replace with winston/pino in prod
const SAFE_BROWSING_ENDPOINT =
"https://safebrowsing.googleapis.com/v4/threatMatches:find";
const MIN_CHROME_VERSION = 148;
/**
* Returns the major Chrome version installed on the host.
* Throws if Chrome cannot be found.
*/
function getChromeVersion(): number {
let output: string;
try {
// Works on Windows, macOS, and most Linux distros
output = execSync("google-chrome --version || chrome --version", {
encoding: "utf8",
}).trim();
} catch (e) {
throw new Error(
"Unable to locate Chrome/Chromium executable. Is it installed and in PATH?"
);
}
// Example: "Google Chrome 148.0.7504.0" or "Chromium 148.0.7504.0"
const versionStr = output.split(" ").pop() ?? "";
const major = parseInt(versionStr.split(".")[0], 10);
if (isNaN(major)) {
throw new Error(`Could not parse version from output: "${output}"`);
}
return major;
}
/**
* Calls Safe Browsing v4 with exponential backoff.
*/
async function checkUrlSafeBrowsing(
url: string,
apiKey: string
): Promise<{ matches?: Array<{ threatType: string; platformType: string }> }> {
const payload = {
client: {
clientId: "icarax-techblog-demo",
clientVersion: "1.0.0",
},
threatInfo: {
threatTypes: [
"MALWARE",
"SOCIAL_ENGINEERING",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION",
],
platformTypes: ["ANY_PLATFORM"],
threatEntryTypes: ["URL"],
threatEntries: [{ url }],
},
};
const attemptFn = () =>
axios.post(SAFE_BROWSING_ENDPOINT, payload, {
params: { key: apiKey },
timeout: 8000,
headers: { "Content-Type": "application/json" },
});
// p-retry will retry on any thrown error (including HTTP errors)
const response = await retry(attemptFn, {
retries: 4,
factor: 2,
minTimeout: 2000,
maxTimeout: 15000,
randomize: true,
});
return response.data;
}
/**
* Main driver – checks Chrome version then scans a list of URLs.
*/
async function main(urls: string[]): Promise<void> {
// ---- 1️⃣ Chrome version check ----
try {
const chromeVer = getChromeVersion();
LOG.info(`Detected Chrome version: ${chromeVer}`);
if (chromeVer < MIN_CHROME_VERSION) {
LOG.error(
`Chrome version ${chromeVer} is below the required minimum ${MIN_CHROME_VERSION}. ` +
"Please update Chrome immediately."
);
process.exit(1);
} else {
LOG.info(`Chrome version satisfies enterprise baseline (≥${MIN_CHROME_VERSION}).`);
}
} catch (err) {
LOG.warn(`Could not determine Chrome version: ${(err as Error).message}`);
LOG.warn("Continuing with Safe Browsing checks only.");
}
// ---- 2️⃣ Safe Browsing checks (if API key present) ----
const apiKey = process.env.SAFE_BROWSING_API_KEY;
if (!apiKey) {
LOG.warn(
"SAFE_BROWSING_API_KEY not set – skipping URL safety checks. " +
"Set it in your environment or a .env file."
);
return;
}
for (const url of urls) {
try {
const result = await checkUrlSafeBrowsing(url, apiKey);
const matches = result.matches ?? [];
if (matches.length) {
LOG.error(
`⚠️ URL ${url} is UNSAFE:\n${JSON.stringify(matches, null, 2)}`
);
} else {
LOG.info(`✅ URL ${url} is clean (no threat matches).`);
}
} catch (err) {
if (axios.isAxiosError(err)) {
const status = err.response?.status;
if (status === 400) {
LOG.error(`Bad request (400) for ${url} – verify payload.`);
} else if (status === 403) {
LOG.error(
`Forbidden (403) for ${url} – likely invalid/missing API key.`
);
} else if (status === 429) {
LOG.error(`Rate limit exceeded (429) for ${url} – back off.`);
} else {
LOG.error(`HTTP ${status} error for ${url}: ${err.message}`);
}
} else {
LOG.error(`Unexpected error checking ${url}:`, err);
}
}
}
}
/* ------------------------------------------------------------------ */
/* Example usage – replace with your own source (file, CLI args, etc.) */
const SAMPLE_URLS = [
"https://www.google.com",
"http://testsafebrowsing.appspot.com/apiv4/ANY_PLATFORM/MALWARE/URL/", // Google's test malware URL
];
main(SAMPLE_URLS).catch((e) => {
console.error("Fatal error:", e);
process.exit(1);
});
# 1️⃣ Install deps (once)
npm install axios p-retry dotnet
# 2️⃣ Create a .env file (or export directly)
echo "SAFE_BROWSING_API_KEY=YOUR_API_KEY_HERE" > .env
# 3️⃣ Execute
npx ts-node chrome-security-check.ts
# or compile first:
# tsc && node dist/chrome-security-check.js
Version‑check‑only: If you omit
SAFE_BROWSING_API_KEY, the script will still validate Chrome ≥ 148 and exit with an error if the requirement isn’t met.
<a name="step-4-configuration"></a>
| Variable | Description | Example | Where to set |
|---|---|---|---|
SAFE_BROWSING_API_KEY | Google Cloud API key for Safe Browsing v4 | AIzaSyA... | Export in shell (export SAFE_BROWSING_API_KEY=…) or .env (Node) |
CHROME_MIN_VERSION (optional) | Override the baseline version (default 148) | 150 | Export or modify constant in code |
LOG_LEVEL (optional) | Verbosity of logs (debug, info, warn, error) | info | Set in code or via env (export LOG_LEVEL=debug) |
REQUEST_TIMEOUT (optional) | Seconds before HTTP request times out | 10 | Adjust in the axios/requests call |
Best practice: Never commit real API keys to source control. Use secret‑management tools (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) in production and inject them at runtime.
<a name="step-5-common-patterns"></a>
# Python – process URLs in chunks of 500 (Safe Browsing free tier limit)
CHUNK_SIZE = 500
for i in range(0, len(urls), CHUNK_SIZE):
chunk = urls[i:i+CHUNK_SIZE]
# build a single request with multiple threatEntries
payload["threatInfo"]["threatEntries"] = [{"url": u} for u in chunk]
# … send request, handle response …
Why? The Safe Browsing API allows up to 500 URLs per request; batching reduces round‑trips and helps stay within quota.
// Node – simple in‑memory TTL cache (use Redis/Memcached in prod)
const cache = new Map();
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 min
async function cachedCheck(url) {
const now = Date.now();
const hit = cache.get(url);
if (hit && now - hit.timestamp < CACHE_TTL_MS) return hit.data;
const data = await checkUrlSafeBrowsing(url, apiKey);
cache.set(url, { timestamp: now, data });
return data;
}
Why? Many applications re‑check the same URLs (e.g., internal link scanners). Caching cuts API calls and latency.
Both snippets already use tenacity (Python) and p-retry (Node) which implement:
wait_exponential(multiplier, min, max)randomize: true (jitter) to avoid thundering herd problems.Replace console/logging with a JSON logger (e.g., pino for Node, structlog for Python) to ship logs to ELK, Splunk, or Cloud Logging for easier alerting.
<a name="step-6-troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
Chrome executable not found | Chrome not installed or not in PATH | Install Chrome/Chromium, or provide full path to binary and modify getChromeVersion() |
400 Bad Request from Safe Browsing | Malformed JSON payload (e.g., missing client field) | Verify payload matches the spec; use the code samples as a reference |
403 Forbidden | Invalid, missing, or restricted API key | Ensure SAFE_BROWSING_API_KEY is correct, has the Safe Browsing API enabled, and is not restricted to incompatible IPs/apps |
429 Too Many Requests | Exceeded quota (free tier: 10 000 checks/day) | Reduce request frequency, enable billing, or request higher quota via Cloud Console |
Empty matches for known bad URL | Using a test domain that isn’t in the list yet | Use Google’s official test URLs: https://developers.google.com/safe-browsing/v4/lookup-api#test-urls |
| Script exits with code 1 after version check | Chrome version < 148 | Update Chrome (google-chrome --version to verify) or adjust MIN_CHROME_VERSION if you intentionally allow older builds (not recommended) |
ECONNREFUSED / network timeout | Outbound traffic blocked by firewall/proxy | Allow outbound HTTPS to safebrowsing.googleapis.com:443; configure proxy if needed (requests.proxies / axios proxy option) |
Debug tip: Set LOG_LEVEL=debug (or enable logging.DEBUG) to see the exact request/response payloads.
<a name="step-7-production-checklist"></a>
| ✅ Item | Why it matters | How to implement |
|---|---|---|
| API key restricted to needed IPs / apps | Prevents key abuse if leaked | In Cloud Console → Credentials → API key → Application restrictions (HTTP referrers, IP addresses, or Android/iOS apps). |
| Enable billing on the project | Free tier is limited; billing avoids sudden 429 errors | Link a billing account; set up budget alerts. |
| Monitor quota usage | Early detection of abuse or mis‑configuration | Use Cloud Monitoring → Safe Browsing API metrics; create alert on quota_exceeded > 80 %. |
| Implement retry with jitter & circuit breaker | Avoids overwhelming the service during outages | Libraries used (tenacity, p-retry) already provide exponential backoff; consider adding a circuit‑breaker (e.g., opossum for Node). |
| Log requests & responses (sans API key) | Auditing and forensic analysis | Strip the key parameter before logging; store logs in a secure, tamper‑evident system. |
| Separate environments | Prevents accidental prod consumption of dev/test keys | Use distinct Google Cloud projects (dev, test, prod) with separate API keys. |
| Version enforcement via enterprise policy | Guarantees all managed clients meet the baseline | Deploy Chrome Enterprise policy VersionTarget or TargetVersionPrefix set to 148.*. |
| Regular dependency updates | Avoids known vulnerabilities in your own libraries | Schedule pip list --outdated / npm audit and apply patches monthly. |
| Test with Google’s Safe Browsing test URLs | Confirms your integration works before relying on it | Include the official test URLs in your CI/CD pipeline; assert they return matches. |
| Document runbooks | Enables fast incident response | Write a short runbook: “If Safe Browsing returns 429 → check quota → request increase or throttle.” |
| Security review | Ensure no accidental leakage of sensitive data | Review that you never log full URLs containing tokens/session IDs; hash or truncate if needed. |
You now have:
Deploy these snippets in your internal tooling, CI pipelines, or endpoint‑security agents to automatically verify that every host is running a patched Chrome and that any URL you process is vetted against Google’s latest threat intelligence—exactly the kind of defense‑in‑depth needed after Chrome 148’s massive security update.
Happy coding, and stay secure! 🚀
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
