fix(ruleset): reject plain lists in ruleset references (#58) and route components to Dushnilin repos

This commit is contained in:
Dushnilin
2026-09-16 15:16:56 +03:00
parent a23c2a2a55
commit d28af621c9
9 changed files with 101 additions and 26 deletions
@@ -1221,9 +1221,9 @@ const COMPONENT_REPO_URLS: Record<Tachyon.ComponentName, string> = {
zapret: 'https://github.com/remittor/zapret-openwrt',
zapret2: 'https://github.com/Dushnilin/zapret2-openwrt',
byedpi: 'https://github.com/DPITrickster/ByeDPI-OpenWrt',
wdtt: 'https://github.com/SpaceNeuroX/qwdtt-openwrt',
olcrtc: 'https://github.com/alekvol/openwrt-olcrtc',
fptn: 'https://github.com/fptn-project/fptn',
wdtt: 'https://github.com/Dushnilin/qwdtt-openwrt',
olcrtc: 'https://github.com/Dushnilin/openwrt-olcrtc',
fptn: 'https://github.com/Dushnilin/fptn',
tailscale: 'https://openwrt.org/packages/pkgdata/tailscale',
direct_bypass: '',
torrserver_direct: '',
@@ -21975,9 +21975,9 @@ var COMPONENT_REPO_URLS = {
zapret: "https://github.com/remittor/zapret-openwrt",
zapret2: "https://github.com/Dushnilin/zapret2-openwrt",
byedpi: "https://github.com/DPITrickster/ByeDPI-OpenWrt",
wdtt: "https://github.com/SpaceNeuroX/qwdtt-openwrt",
olcrtc: "https://github.com/alekvol/openwrt-olcrtc",
fptn: "https://github.com/fptn-project/fptn",
wdtt: "https://github.com/Dushnilin/qwdtt-openwrt",
olcrtc: "https://github.com/Dushnilin/openwrt-olcrtc",
fptn: "https://github.com/Dushnilin/fptn",
tailscale: "https://openwrt.org/packages/pkgdata/tailscale",
direct_bypass: "",
torrserver_direct: ""
@@ -7648,6 +7648,12 @@ function validateFileReference(value, extensions, errorMessage, options = {}) {
}
function validateCustomRulesetReference(value) {
if (hasAllowedReferenceExtension(value, [".lst", ".txt"])) {
return _(
'Plain .lst / .txt lists belong in "Domain and IP lists", not "Rule sets"',
);
}
return validateFileReference(
value,
[".srs", ".json"],
+55 -19
View File
@@ -1352,10 +1352,18 @@ function resolve_byedpi_release(arch, tag) {
function resolve_wdtt_release(arch, tag) {
let asset_ext = is_apk() ? "apk" : "ipk";
let release_series = trim(helper_output("openwrt-release-series", [ "/etc/openwrt_release" ]));
let owner = "Dushnilin";
let repo = "qwdtt-openwrt";
let releases_json = (tag != null && tag != "") ?
fetch_github_release_by_tag_json("SpaceNeuroX", "qwdtt-openwrt", tag) :
fetch_github_releases_json("SpaceNeuroX", "qwdtt-openwrt", "30");
if (releases_json != "") {
fetch_github_release_by_tag_json(owner, repo, tag) :
fetch_github_releases_json(owner, repo, "30");
if (releases_json == "" || releases_json == "[]") {
owner = "SpaceNeuroX";
releases_json = (tag != null && tag != "") ?
fetch_github_release_by_tag_json(owner, repo, tag) :
fetch_github_releases_json(owner, repo, "30");
}
if (releases_json != "" && releases_json != "[]") {
let resolved = trim(helper_output_input(releases_json, "wdtt-select-asset", [ release_series, asset_ext, arch.candidates ]));
let fields = split(resolved, "\t");
if (length(fields) >= 4) {
@@ -1372,11 +1380,13 @@ function resolve_wdtt_release(arch, tag) {
let distrib_arch = read_openwrt_release_value("DISTRIB_ARCH");
let tag_clean = replace(tag, /^v/, "");
let pkg_name = "wdtt_" + tag_clean + "_openwrt_" + distrib_arch + "." + asset_ext;
let dushnilin_url = "https://github.com/Dushnilin/qwdtt-openwrt/releases/download/" + tag + "/" + pkg_name;
let target_owner = url_exists(dushnilin_url) ? "Dushnilin" : "SpaceNeuroX";
return {
arch: distrib_arch,
package_name: pkg_name,
package_url: "https://github.com/SpaceNeuroX/qwdtt-openwrt/releases/download/" + tag + "/" + pkg_name,
release_url: "https://github.com/SpaceNeuroX/qwdtt-openwrt/releases/tag/" + tag,
package_url: "https://github.com/" + target_owner + "/qwdtt-openwrt/releases/download/" + tag + "/" + pkg_name,
release_url: "https://github.com/" + target_owner + "/qwdtt-openwrt/releases/tag/" + tag,
version: tag
};
}
@@ -1386,10 +1396,18 @@ function resolve_wdtt_release(arch, tag) {
function resolve_olcrtc_release(arch, tag) {
let asset_ext = is_apk() ? "apk" : "ipk";
let release_series = trim(helper_output("openwrt-release-series", [ "/etc/openwrt_release" ]));
let owner = "Dushnilin";
let repo = "openwrt-olcrtc";
let releases_json = (tag != null && tag != "") ?
fetch_github_release_by_tag_json("alekvol", "openwrt-olcrtc", tag) :
fetch_github_releases_json("alekvol", "openwrt-olcrtc", "30");
if (releases_json != "") {
fetch_github_release_by_tag_json(owner, repo, tag) :
fetch_github_releases_json(owner, repo, "30");
if (releases_json == "" || releases_json == "[]") {
owner = "alekvol";
releases_json = (tag != null && tag != "") ?
fetch_github_release_by_tag_json(owner, repo, tag) :
fetch_github_releases_json(owner, repo, "30");
}
if (releases_json != "" && releases_json != "[]") {
let resolved = trim(helper_output_input(releases_json, "olcrtc-select-asset", [ release_series, asset_ext, arch.candidates ]));
let fields = split(resolved, "\t");
if (length(fields) >= 4) {
@@ -1406,11 +1424,13 @@ function resolve_olcrtc_release(arch, tag) {
let distrib_arch = read_openwrt_release_value("DISTRIB_ARCH");
let tag_clean = replace(tag, /^v/, "");
let pkg_name = "olcrtc_" + tag_clean + "_openwrt_" + distrib_arch + "." + asset_ext;
let dushnilin_url = "https://github.com/Dushnilin/openwrt-olcrtc/releases/download/" + tag + "/" + pkg_name;
let target_owner = url_exists(dushnilin_url) ? "Dushnilin" : "alekvol";
return {
arch: distrib_arch,
package_name: pkg_name,
package_url: "https://github.com/alekvol/openwrt-olcrtc/releases/download/" + tag + "/" + pkg_name,
release_url: "https://github.com/alekvol/openwrt-olcrtc/releases/tag/" + tag,
package_url: "https://github.com/" + target_owner + "/openwrt-olcrtc/releases/download/" + tag + "/" + pkg_name,
release_url: "https://github.com/" + target_owner + "/openwrt-olcrtc/releases/tag/" + tag,
version: tag
};
}
@@ -1420,10 +1440,18 @@ function resolve_olcrtc_release(arch, tag) {
function resolve_fptn_release(arch, tag) {
let asset_ext = is_apk() ? "apk" : "ipk";
let release_series = trim(helper_output("openwrt-release-series", [ "/etc/openwrt_release" ]));
let owner = "Dushnilin";
let repo = "fptn";
let releases_json = (tag != null && tag != "") ?
fetch_github_release_by_tag_json("fptn-project", "fptn", tag) :
fetch_github_releases_json("fptn-project", "fptn", "30");
if (releases_json != "") {
fetch_github_release_by_tag_json(owner, repo, tag) :
fetch_github_releases_json(owner, repo, "30");
if (releases_json == "" || releases_json == "[]") {
owner = "fptn-project";
releases_json = (tag != null && tag != "") ?
fetch_github_release_by_tag_json(owner, repo, tag) :
fetch_github_releases_json(owner, repo, "30");
}
if (releases_json != "" && releases_json != "[]") {
let resolved = trim(helper_output_input(releases_json, "fptn-select-asset", [ release_series, asset_ext, arch.candidates ]));
let fields = split(resolved, "\t");
if (length(fields) >= 4) {
@@ -1443,11 +1471,13 @@ function resolve_fptn_release(arch, tag) {
let distrib_arch = read_openwrt_release_value("DISTRIB_ARCH");
let tag_clean = replace(tag, /^v/, "");
let pkg_name = "fptn-client-" + tag_clean + "-openwrt-" + (release_series != "" ? release_series + ".x" : "24.10.x") + "-" + distrib_arch + "." + asset_ext;
let dushnilin_url = "https://github.com/Dushnilin/fptn/releases/download/" + tag + "/" + pkg_name;
let target_owner = url_exists(dushnilin_url) ? "Dushnilin" : "fptn-project";
return {
arch: distrib_arch,
package_name: pkg_name,
package_url: "https://github.com/fptn-project/fptn/releases/download/" + tag + "/" + pkg_name,
release_url: "https://github.com/fptn-project/fptn/releases/tag/" + tag,
package_url: "https://github.com/" + target_owner + "/fptn/releases/download/" + tag + "/" + pkg_name,
release_url: "https://github.com/" + target_owner + "/fptn/releases/tag/" + tag,
version: tag_clean
};
}
@@ -3498,17 +3528,23 @@ function list_component_releases(component, count) {
} else if (component == "byedpi") {
owner = "DPITrickster"; repo = "ByeDPI-OpenWrt";
} else if (component == "wdtt") {
owner = "SpaceNeuroX"; repo = "qwdtt-openwrt";
owner = "Dushnilin"; repo = "qwdtt-openwrt";
} else if (component == "olcrtc") {
owner = "alekvol"; repo = "openwrt-olcrtc";
owner = "Dushnilin"; repo = "openwrt-olcrtc";
} else if (component == "fptn") {
owner = "fptn-project"; repo = "fptn";
owner = "Dushnilin"; repo = "fptn";
} else {
print("[]\n"); return;
}
let releases_json = fetch_github_releases_json(owner, repo, per_page);
if (releases_json == "") { print("[]\n"); return; }
if ((releases_json == "" || releases_json == "[]") && (component == "wdtt" || component == "olcrtc" || component == "fptn")) {
if (component == "wdtt") { owner = "SpaceNeuroX"; repo = "qwdtt-openwrt"; }
else if (component == "olcrtc") { owner = "alekvol"; repo = "openwrt-olcrtc"; }
else if (component == "fptn") { owner = "fptn-project"; repo = "fptn"; }
releases_json = fetch_github_releases_json(owner, repo, per_page);
}
if (releases_json == "" || releases_json == "[]") { print("[]\n"); return; }
let releases = [];
try {
+15 -1
View File
@@ -337,6 +337,18 @@ function remote_reference(value) {
return string_starts_with(value, "http://") || string_starts_with(value, "https://");
}
function remote_plain_list_reference(value) {
value = lc(as_string(value));
let query = index(value, "?");
if (query >= 0)
value = substr(value, 0, query);
let fragment = index(value, "#");
if (fragment >= 0)
value = substr(value, 0, fragment);
return string_ends_with(value, ".lst") || string_ends_with(value, ".txt");
}
function absolute_reference_with_extension(value, first_extension, second_extension) {
value = as_string(value);
if (!string_starts_with(value, "/"))
@@ -351,7 +363,7 @@ function ruleset_reference_valid(reference, community_services) {
return reference == "" ||
community_service_valid(reference, community_services) ||
remote_reference(reference) ||
(remote_reference(reference) && !remote_plain_list_reference(reference)) ||
absolute_reference_with_extension(reference, ".srs", ".json");
}
@@ -1211,6 +1223,8 @@ function validate_service_value(service, context) {
}
function validate_ruleset_reference_value(reference, context) {
if (remote_plain_list_reference(reference))
fail_validation("Plain .lst/.txt list '" + reference + "' belongs in domain_ip_lists, not rule_set. Aborted.");
if (ruleset_reference_valid(reference, context.community_services))
return;
@@ -952,6 +952,8 @@ function ensure_custom_ruleset(config, reference) {
return { tag: tag_name, kind };
let extension = runtime_rulesets.file_extension(reference);
if (runtime_rulesets.is_plain_list_reference(reference))
ctx.runtime_generate_unsupported("plain .lst/.txt list '" + reference + "' belongs in domain_ip_lists, not rule_set");
if (substr(reference, 0, 1) == "/") {
if (extension != "srs" && extension != "json")
ctx.runtime_generate_unsupported("local rule_set extension is not supported by sing-box config generation");
@@ -134,6 +134,11 @@ function remote_format(reference) {
return file_extension(reference) == "json" ? "source" : "binary";
}
function is_plain_list_reference(reference) {
let extension = file_extension(reference);
return extension == "lst" || extension == "txt";
}
function is_valid_srs_file(path) {
let p = as_string(path);
let st = fs.stat(p);
@@ -180,6 +185,7 @@ function module_exports() {
file_extension,
kind_from_reference_hint,
remote_format,
is_plain_list_reference,
is_valid_srs_file,
ensure_empty_srs_stub
};
@@ -198,6 +204,8 @@ else if (mode == "kind-from-reference-hint")
print(kind_from_reference_hint(ARGV[1]), "\n");
else if (mode == "remote-format")
print(remote_format(ARGV[1]), "\n");
else if (mode == "is-plain-list-reference")
exit(is_plain_list_reference(ARGV[1]) ? 0 : 1);
else if (mode == "is-valid-srs-file")
exit(is_valid_srs_file(ARGV[1]) ? 0 : 1);
else if (mode == "ensure-empty-srs-stub")
+3
View File
@@ -34,6 +34,9 @@ assert_rejects "unknown community service" community-service-valid unknown "$COM
assert_accepts "empty ruleset reference" ruleset-reference-valid "" "$COMMUNITY_SERVICES"
assert_accepts "community ruleset reference" ruleset-reference-valid telegram "$COMMUNITY_SERVICES"
assert_accepts "https ruleset reference" ruleset-reference-valid https://example.com/rule.srs "$COMMUNITY_SERVICES"
assert_accepts "extensionless https ruleset reference" ruleset-reference-valid https://example.com/download "$COMMUNITY_SERVICES"
assert_rejects "https plain list as ruleset reference" ruleset-reference-valid https://example.com/list.lst "$COMMUNITY_SERVICES"
assert_rejects "https plain list with query as ruleset reference" ruleset-reference-valid "https://example.com/list.txt?token=1" "$COMMUNITY_SERVICES"
assert_accepts "absolute srs ruleset reference" ruleset-reference-valid /tmp/rule.srs "$COMMUNITY_SERVICES"
assert_accepts "absolute json ruleset reference" ruleset-reference-valid /tmp/rule.json "$COMMUNITY_SERVICES"
assert_rejects "relative ruleset reference" ruleset-reference-valid relative/rule.srs "$COMMUNITY_SERVICES"
+6
View File
@@ -45,6 +45,12 @@ assert_eq binary \
"$(ucode -L "$TACHYON_LIB" "$RULESETS_UC" remote-format 'https://example.com/rules.unknown')" \
"unknown remote ruleset format"
ucode -L "$TACHYON_LIB" "$RULESETS_UC" is-plain-list-reference 'https://example.com/list.lst?token=1' ||
fail "plain .lst URL should be recognized as a plain list"
if ucode -L "$TACHYON_LIB" "$RULESETS_UC" is-plain-list-reference 'https://example.com/rules.json'; then
fail ".json URL should not be recognized as a plain list"
fi
ucode -L "$TACHYON_LIB" -e 'let rulesets = require("singbox.rulesets"); if (rulesets.kind_from_reference_hint("geoip") != "subnets") exit(1);'
ucode -L "$TACHYON_LIB" -e 'let rulesets = require("singbox.rulesets"); if (type(rulesets.COMMUNITY_SERVICES) != "object" || !rulesets.COMMUNITY_SERVICES.discord) exit(1);'
ucode -L "$TACHYON_LIB" -e 'let rulesets = require("singbox.rulesets"); if (!rulesets.COMMUNITY_SERVICES.twitch) exit(1);'