返回 DeepSeek-Reasonix
inspect-cloudflare-download-access.mjs
根目录 / scripts / inspect-cloudflare-download-access.mjs
1 // Read-only diagnostics. The token stays in the runner and is sent only to the
2 // Cloudflare API; reports omit visitor identities, response bodies, and rules.
3 import { createHash } from "node:crypto";
4
5 const token = process.env.CLOUDFLARE_API_TOKEN;
6 if (!token) throw new Error("CLOUDFLARE_API_TOKEN is not configured");
7 const api = "https://api.cloudflare.com/client/v4";
8 const report = (kind, data) => console.log(JSON.stringify({ kind, ...data }));
9
10 function errorClass(error) {
11 const message = String(error.message || "");
12 if (/cannot query field|unknown field|not defined by type|unknown argument/i.test(message)) return "query-schema";
13 if (/permission|unauthori[sz]ed|access denied|authentication|forbidden/i.test(message)) return "permission";
14 if (/cannot request data older|maximum.*(window|duration)|time range/i.test(message)) return "query-window";
15 return "unknown";
16 }
17
18 async function request(endpoint, body) {
19 const response = await fetch(`${api}${endpoint}`, {
20 method: body ? "POST" : "GET",
21 headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
22 ...(body ? { body: JSON.stringify(body) } : {}),
23 signal: AbortSignal.timeout(30000),
24 redirect: "error",
25 });
26 const json = await response.json();
27 if (!response.ok || json.success === false || json.errors?.length) {
28 // Error messages may contain visitor data. Publish only codes and classes.
29 report("api-unavailable", { endpoint, status: response.status,
30 errors: (json.errors || []).map(error => ({ code: Number.isInteger(error.code) ? error.code : null, class: errorClass(error) })) });
31 return null;
32 }
33 return json;
34 }
35
36 for (const [profile, userAgent] of [
37 ["node-default", undefined],
38 ["desktop-updater-current", "Reasonix-Updater/v1.38.11 (linux/amd64; build=stable; update=stable)"],
39 ["desktop-updater-target", "Reasonix-Updater/v1.38.12 (linux/amd64; build=stable; update=stable)"],
40 ]) {
41 const probe = await fetch("https://dl.reasonix.io/latest/latest.json", {
42 headers: userAgent ? { "User-Agent": userAgent } : undefined,
43 signal: AbortSignal.timeout(30000), redirect: "error",
44 });
45 report("public-manifest", {
46 profile,
47 status: probe.status,
48 mitigation: probe.headers.get("cf-mitigated"),
49 ray: probe.headers.get("cf-ray"),
50 contentType: probe.headers.get("content-type"),
51 });
52 await probe.body?.cancel();
53 }
54
55 const zones = await request("/zones?name=reasonix.io&status=active");
56 const matches = zones?.result?.filter(zone => zone.name === "reasonix.io");
57 if (matches?.length !== 1) {
58 throw new Error("Cannot identify reasonix.io with this token; Zone Read permission is required");
59 }
60 const zoneID = matches[0].id;
61 report("zone", { name: "reasonix.io", id: zoneID });
62
63 for (const phase of ["http_config_settings", "http_request_firewall_custom", "http_request_firewall_managed", "http_request_sbfm"]) {
64 const response = await request(`/zones/${zoneID}/rulesets/phases/${phase}/entrypoint`);
65 if (!response) continue;
66 const ruleset = response.result;
67 report("ruleset", {
68 phase, id: ruleset.id,
69 rules: (ruleset.rules || []).map(rule => ({
70 id: rule.id, action: rule.action, enabled: rule.enabled !== false,
71 expressionSHA256: createHash("sha256").update(rule.expression || "").digest("hex"),
72 })),
73 });
74 }
75 for (const setting of ["security_level", "browser_check"]) {
76 const response = await request(`/zones/${zoneID}/settings/${setting}`);
77 if (response) report("setting", { setting, value: response.result.value, editable: response.result.editable });
78 }
79 const bots = await request(`/zones/${zoneID}/bot_management`);
80 if (bots) report("bot-management", {
81 fightMode: bots.result.fight_mode,
82 definitelyAutomated: bots.result.sbfm_definitely_automated,
83 likelyAutomated: bots.result.sbfm_likely_automated,
84 });
85
86 const events = await request("/graphql", {
87 query: `query ManifestChallenges($zoneTag: string, $filter: FirewallEventsAdaptiveFilter_InputObject) {
88 viewer { zones(filter: { zoneTag: $zoneTag }) {
89 firewallEventsAdaptive(filter: $filter, limit: 500, orderBy: [datetime_DESC]) {
90 action source datetime clientRequestHTTPHost clientRequestPath
91 }
92 } }
93 }`,
94 variables: {
95 zoneTag: zoneID,
96 filter: {
97 datetime_geq: new Date(Date.now() - 10 * 60 * 1000).toISOString(),
98 datetime_leq: new Date().toISOString(),
99 },
100 },
101 });
102 if (events) {
103 const entries = events.data?.viewer?.zones?.[0]?.firewallEventsAdaptive ?? [];
104 const matching = entries.filter(event => event.clientRequestHTTPHost === "dl.reasonix.io" && event.clientRequestPath === "/latest/latest.json");
105 report("manifest-security-events", { inspected: entries.length,
106 events: matching.map(({ action, source, datetime }) => ({ action, source, datetime })) });
107 }
108
108 lines Plain Text