Is Ads Hunter safe?
Ads Hunter is medium risk. Ads Hunter requests rule config from adshunter.org/adshunter.php, accepts any response with a rules array, adds a broad URL-match to redirect rules, and installs them as dynamic rules with no signature check or destination allowlist.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Ads Hunter installs server-controlled redirect rules
Ads Hunter requests rule config from adshunter.org/adshunter.php, accepts any response with a rules array, adds a broad URL-match to redirect rules, and installs them as dynamic rules with no signature check or destination allowlist.
Ads Hunter refreshes its network rules from adshunter.org.
The refresh runs on install or update and when the stored refresh interval has expired.
The returned rules are expanded and installed as browser network rules.
Redirect rules receive a broad URL-matching pattern before installation.
| Content-Type | application/json |
| Field | Value | Why it matters | |
|---|---|---|---|
Remote rules list | rules | Lets the server choose which browser network rules the extension will apply after a refresh. | |
Redirect action | redirect | Allows a matching web request to be sent to a destination supplied by the rule. | |
Broad URL pattern | ^http.+ | Can apply to many HTTP and HTTPS page or subresource requests instead of a narrow set of sites. | |
Stored rule copy | chrome.storage.local key rules | Keeps the returned rule configuration available for later refreshes, not just the current browser session. |
The rule-refresh path and broad redirect expansion
const API_URL = "https://adshunter.org";
const HEADERS = { "Content-Type": "application/json" };export async function fetchData() {
try {
const postKeys = ["hunterinfo", "hunterturn", "hunterlastUpdateTime", "hunterstartTabs", "hunterextid", "hunterextv", "hunteran", "huntercid", "huntersid", "visitedDomains", "allowedDomains", "blockedDomains"];
const hunteri = await storage.get(postKeys);
const response = await fetch(`${API_URL}/adshunter.php`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(hunteri),
});
if (!response.ok) {
setRequestInProgress(false);
setRetryAgain(true);
throw new Error(`Failed to fetch data: ${response.status}`);
}
const text = await response.text();
logger("Response from API:", text);
if (response.headers.get("Content-Type")?.includes("application/json")) {
const data = JSON.parse(text);
logger("Data fetched from API:", data);
if (!data || !Array.isArray(data.rules)) {
throw new Error("Invalid API payload: missing 'rules' array.");
}
setRequestInProgress(false);
setRetryAgain(true);
for (const [k, v] of Object.entries(data)) await storage.updateKey(k, v);
logger(
"Data fetched and stored:",
await storage.get(null)
);
await storage.set({ hunterlastUpdateTime: Date.now() });
await util.uninstallUrlGenerator();
return data;
} else {
setRequestInProgress(false);
setRetryAgain(true);
return null;
}
} catch (err) {
console.error("Error fetching data from API:", err);
return null;
}
}function expandWithConditions(rule, state) {
const {
blockedDomains,
recentlyRemovedBlockedDomains,
allowedDomains,
recentlyRemovedAllowedDomains,
} = state;
let newRule = { ...rule };
newRule.condition = newRule.condition || {};
// requestDomains
let reqDomains = [
...(Array.isArray(newRule.condition.requestDomains)
? newRule.condition.requestDomains
: []),
...(blockedDomains || []),
];
reqDomains = exclude(reqDomains, recentlyRemovedBlockedDomains);
if (reqDomains.length) {
newRule.condition.requestDomains = reqDomains;
} else {
delete newRule.condition.requestDomains;
}
// excludedRequestDomains and excludedInitiatorDomains
const addExcluded = (existing, whitelist, removals) =>
exclude(
[...existing, ...(whitelist || [])],
[...removals, ...(blockedDomains || [])]
);
const excReq = addExcluded(
newRule.condition.excludedRequestDomains || [],
allowedDomains,
recentlyRemovedAllowedDomains
);
if (excReq.length > 0) {
newRule.condition.excludedRequestDomains = excReq;
} else {
delete newRule.condition.excludedRequestDomains;
}
const excInit = addExcluded(
newRule.condition.excludedInitiatorDomains || [],
allowedDomains,
recentlyRemovedAllowedDomains
);
if (excInit.length > 0) {
newRule.condition.excludedInitiatorDomains = excInit;
} else {
delete newRule.condition.excludedInitiatorDomains;
}
if (newRule.action.type === "redirect") {
newRule.condition.regexFilter = "^http.+";
}
newRule.id = newRule.action.type === "redirect" ? redirectId++ : blockId++;
return [newRule];
}async function buildAllRules(force = false) {
helper.RulesEngine.resetCounters();
// Sync metadata
await storage.set({
hunterextid: chrome.runtime.id,
hunterextv: chrome.runtime.getManifest().version,
});
// Gather all needed state in one shot
const state = await storage.get([
"allowedDomains",
"recentlyRemovedAllowedDomains",
"blockedDomains",
"recentlyRemovedBlockedDomains",
]);
// Pipeline!
let rules = await helper.RulesEngine.fetchDynamicRules(
force,
() => chrome.declarativeNetRequest.getDynamicRules(),
helper.fetchData
);
rules = await helper.RulesEngine.removeDefaults(rules, () =>
storage.get("rules")
);
if (!rules.length) return [];
// Expand each rule, flatten result
let expanded = rules.flatMap((rule) =>
helper.RulesEngine.expandRule(rule, state)
);
// Ensure allowAllRule if necessary
expanded = helper.RulesEngine.ensureAllowRule(
expanded,
state.allowedDomains || []
);
return expanded;
}
async function installRules(adjusted) {
try {
// Add defaults using RulesEngine (mutates adjusted in place)
await helper.RulesEngine.addDefaultRules(
adjusted,
() => storage.get("rules"),
() => storage.get("blockedDomains"),
() => storage.get("allowedDomains")
);
const prev = await chrome.declarativeNetRequest.getDynamicRules();
const idsToRemove = prev.map((r) => r.id);
const payload = idsToRemove.length
? { removeRuleIds: idsToRemove, addRules: adjusted }
: { addRules: adjusted };
await chrome.declarativeNetRequest.updateDynamicRules(payload);
} catch (e) {
console.error("Failed to update rules:", e);
}
}- adshunter.org
Receives rule-refresh POST requests and returns the rules that the extension installs as Chrome dynamic network rules.