How can a website and a download look clean in several public checks while an advertising account is still rejected for malware? The contradiction is easier to understand when the ad campaign is treated as a delivery system rather than as a single web page.
A current Hacker News discussion points to a useful case. The author of RACE, a native macOS terminal multiplexer written in Rust, reported that a Google Ads account was suspended after spending USD 500 on a campaign. The notice named "Malicious software" and "Compromised Site." The author then checked the website, the download infrastructure, the application, and several public security services, but the appeals still did not reveal which observation caused the decision.
That report does not prove that Google made a mistake, and it does not reveal how the classifier worked. It does show why a publisher needs a record of the whole path from ad impression to first launch. A clean result from one layer is evidence about that layer. It is not a universal verdict about every layer that a visitor may cross.
What the RACE case actually shows
RACE is described as a terminal multiplexer that keeps shell sessions alive across application restarts. Its website is a static Bridgetown site, with downloads hosted separately. The author says it had no custom server-side application at submission time.
The advertising attempt produced a suspension rather than a useful diagnostic. The notice named malicious software and a compromised site. Repeated appeals were rejected, and one path was blocked for a week. The review covered Safe Browsing, Search Console, VirusTotal, signatures, notarization, JavaScript bundles, logs, and different user agents.
One detail matters more than the number of checks. RACE starts and manages background shell processes because process persistence is part of a terminal multiplexer. That behavior can look unusual to a detector even when documented. The report says the application did not inject code into other applications, change browser behavior, or conceal its process activity.
Those are observations from the publisher's investigation, not the platform's internal reason. "The public checks were clean" is defensible; "the campaign was proven safe" is not supported by the same evidence.
Why clean scanners do not clear an ad campaign
The checks in this case answer different questions. Google Safe Browsing examines URLs and warns users in Search and browsers when it detects unsafe sites. Its public status tool can tell a publisher what it reports about a URL at the time of the check. It does not promise that the URL will be treated the same way by an advertising review.
Search Console's Security issues report has a different purpose. Google says the report presents findings when an evaluation determines that a site was hacked or shows behavior that could harm a visitor or their computer. The report can include sample affected URLs, but Google also says the sample may not be complete and that some issues have no example URL. A green result in that report is therefore useful evidence about the report, not a signed clearance for every ad destination and binary.
Apple notarization answers a third question for macOS software. Apple describes its notary service as an automated system that scans for malicious components, checks code-signing issues, and returns a ticket that Gatekeeper can find. Apple also says notarization is not App Review. A valid Developer ID signature and ticket help a user evaluate an application, but they do not determine whether an ad platform accepts a campaign.
Google Ads documentation says reviews may use multiple sources, including the ad, website, accounts, and third-party sources. It does not identify the RACE signal, but it explains why independent reports can disagree. A policy system can inspect more than a URL scanner or notarization service.
The outputs are partial observations. An ad review can consider the relationship between the account, creative, destination, redirects, download, and prior activity. Treating the outputs as interchangeable creates false confidence.
Model the download path as a changing state machine
A publisher should record the path as a sequence of states, not as a screenshot of a landing page. A practical sequence is search query, ad, landing URL, HTML response, script and redirect chain, download host, archive, signed application, first launch, and process or network behavior.
Each state needs a timestamp, URL or artifact identity, observation method, and result. If the download changes while the ad remains unchanged, the reviewed object may no longer be the delivered object. A redirect can also vary by referrer, region, user agent, or cookie. These are engineering possibilities, not claims about the RACE classifier.
A small record type makes the distinction concrete:
from dataclasses import dataclass
from datetime import datetime, timezone
@dataclass(frozen=True)
class Observation:
stage: str
url: str
status: int | None
artifact_sha256: str | None
observed_at: str
method: str
def timestamp() -> str:
return datetime.now(timezone.utc).isoformat()
record = Observation(
stage="landing-page",
url="https://downloads.example.invalid/",
status=200,
artifact_sha256=None,
observed_at=timestamp(),
method="isolated-http-client",
)
The record does not make a verdict. It preserves context for comparing a later fetch with the earlier one, which matters when an appeal asks which release or response was examined.
A state-machine view prevents a category error. A signed file can be safe at rest while a landing page is compromised, or a clean page can redirect to a different download. A legitimate application can also launch a helper that was not in the original hash.
Verify redirects and artifacts without trusting one layer
The first pass should collect metadata without launching the program. Google’s security guidance warns against opening infected pages directly in a browser and recommends safer methods for examining responses. Use an isolated HTTP client and disposable environment, not a daily workstation.
The redirect chain should be preserved, not reduced to the final URL. Record status codes, location headers, response headers, the request method, and the user agent. Run the same capture from a clean environment when the result matters, then compare the chains rather than choosing the most reassuring one.
from urllib.request import Request, build_opener, HTTPRedirectHandler
class TraceRedirects(HTTPRedirectHandler):
def __init__(self):
self.events = []
def redirect_request(self, request, file, code, message, headers, new_url):
self.events.append({
"from": request.full_url,
"status": code,
"location": new_url,
})
return super().redirect_request(request, file, code, message, headers, new_url)
def fetch_with_trace(url: str) -> tuple[str, list[dict]]:
tracer = TraceRedirects()
opener = build_opener(tracer)
request = Request(url, headers={"User-Agent": "release-audit/1.0"})
with opener.open(request, timeout=20) as response:
response.read(4096)
return response.geturl(), tracer.events
This is a capture tool, not a bypass for an advertising review. Do not rotate identities to evade a policy control. Document the ordinary path a reviewer can reproduce and note whether the destination changes.
The file itself needs an identity. A filename is not enough because a publisher can replace the bytes while keeping the same name. Hash the exact archive that was uploaded, hash the extracted executable when appropriate, and keep the command output with the release record.
from hashlib import sha256
from pathlib import Path
def sha256_file(path: str) -> str:
digest = sha256()
with Path(path).open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
archive_hash = sha256_file("RACE.dmg")
print({"file": "RACE.dmg", "sha256": archive_hash})
For a macOS release, keep the signing identity, entitlements, notarization result, and hash of the notarized deliverable. If a ticket is stapled after hashing, the reviewed object may differ from the object offered to users.
Build an evidence bundle for a manual review
A useful appeal package is a reproducible bundle, not a pile of screenshots. Identify the ad text, destination, time window, redirect chain, response headers, HTML and script hashes, download hash, signature details, notarization status, and observation environment.
Separate facts from interpretation. An observed field can contain a status code or hash; a hypothesis field can say that a background process may have looked unusual. This keeps an explanation from being mistaken for a platform finding.
import json
from dataclasses import asdict, dataclass
from pathlib import Path
@dataclass
class ReviewBundle:
ad_id: str
landing_url: str
final_url: str
artifact_sha256: str
observed: list[dict]
hypotheses: list[str]
def write_bundle(bundle: ReviewBundle, output: str) -> None:
Path(output).write_text(
json.dumps(asdict(bundle), indent=2, sort_keys=True),
encoding="utf-8",
)
bundle = ReviewBundle(
ad_id="campaign-record-2026-09-09",
landing_url="https://race-term.example.invalid/",
final_url="https://downloads.example.invalid/RACE.dmg",
artifact_sha256="record-after-download",
observed=[{"source": "isolated-http-client", "status": 200}],
hypotheses=["documented process persistence may need an explicit explanation"],
)
write_bundle(bundle, "review-bundle.json")
The example values are placeholders and must not be submitted as evidence. In a real bundle, do not redact the artifact identity or the time of observation, but do remove cookies, access tokens, and private account data. Keep the original response bytes and logs in a protected archive so the summary can be regenerated.
This format also makes version changes visible. If the binary, redirect chain, or JavaScript bundle changes after an appeal, create a new bundle rather than editing the old one. A reviewer should be able to tell whether the publisher is defending the same object or a later release.
What ad platforms should expose to developers
The RACE report points to a product problem as well as a publisher problem: a generic label leaves no path to test the alleged fault. A review system should expose the category of object that triggered the decision, even if it cannot reveal a sensitive classifier: ad, destination, redirect, download, executable, account context, or third-party report.
It should provide the observation time, sampled URL, response status, artifact hash when available, and environment class. It should distinguish a page warning from an account suspension. If a third-party signal was involved, the publisher needs enough information to identify the asset without seeing private detection rules.
Search Console acknowledges that sample URLs may be incomplete or absent. An appeal interface should show that limit rather than treating a missing sample as proof that nothing was checked, or a clean public report as proof that the ad review had no other input.
That would help both sides: a real compromise would be easier to reproduce and fix, while a false positive would be easier to isolate. The platform could protect its detection logic while giving the publisher a stable object to inspect.
A safer publishing checklist
Freeze the release. Record the exact ad text, destination URL, redirect chain, archive hash, executable hash, signature identity, and notarization result before submitting a campaign.
Capture the normal path with an isolated HTTP client. Preserve redirects, response headers, and final URLs. Do not open an unknown page or program on the workstation used for credentials and daily work.
Check the layers separately. Run Safe Browsing, review Search Console security findings, inspect the download, and verify the platform-specific signing process. Store each result with its timestamp and scope.
Explain unusual behavior plainly. A terminal multiplexer that keeps shell processes alive should document that behavior, its configuration options, and its cleanup path. Documentation does not prove safety, but silence makes a legitimate behavior harder to review.
Submit a factual appeal. State what was observed, what was not observed, and what remains unknown. Do not call a clean scan a certificate, and do not claim to know the classifier's reason without a platform response.
Keep every appeal tied to an immutable bundle. If the binary or destination changes, start a new record, and ask which object and observation must change before a new review can be meaningful.
The RACE case does not establish that Google Ads misclassified a legitimate application. It establishes a narrower engineering lesson: security evidence is scoped to the system that produced it. Safe Browsing, Search Console, Apple notarization, file scanners, and ad review can each observe a different part of the same delivery path. Publishers need reproducible artifacts and a clear separation between observation and theory; platforms need review signals that let a legitimate publisher find the disputed state.
Further reading: the RACE case report, Google Ads policy documentation, Search Console security issues, Apple notarization documentation, and Google Safe Browsing status.
Originally published on Dispatch.
Top comments (0)