From a41fc3e266ef7b023a539f4112837fb9d88b12cb Mon Sep 17 00:00:00 2001 From: Yuriy Date: Wed, 18 Mar 2026 23:46:54 +0200 Subject: [PATCH 1/8] n8n testbeds --- n8n/exposed/README.md | 28 +++++++++++++++++++++++++ n8n/exposed/docker-compose-non-vuln.yml | 19 +++++++++++++++++ n8n/exposed/docker-compose-vuln.yml | 19 +++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 n8n/exposed/README.md create mode 100644 n8n/exposed/docker-compose-non-vuln.yml create mode 100644 n8n/exposed/docker-compose-vuln.yml diff --git a/n8n/exposed/README.md b/n8n/exposed/README.md new file mode 100644 index 00000000..e8e23e30 --- /dev/null +++ b/n8n/exposed/README.md @@ -0,0 +1,28 @@ +# n8n Exposed REST API + +n8n is a workflow automation platform that allows users to create and execute automated workflows. +This testbed demonstrates exposed and properly secured configurations of the n8n REST API. + +## Safe setup + +```shell +docker compose -f docker-compose-non-vuln.yml up -d +``` +This configuration enforces authentication and prevents unauthorized access to the REST API. + +## Vulnerable setup + +```shell +docker compose -f docker-compose-vuln.yml up -d +``` +This configuration exposes the REST API without authentication, allowing unauthorized access. + +## Notes + +- In older versions, exposure may occur when authentication is disabled. +- In newer versions, this issue is often caused by misconfigurations such as improperly secured reverse proxies. + +## References + +- https://github.com/n8n-io/n8n +- https://docs.n8n.io/hosting/securing/overview/ diff --git a/n8n/exposed/docker-compose-non-vuln.yml b/n8n/exposed/docker-compose-non-vuln.yml new file mode 100644 index 00000000..c52b4dca --- /dev/null +++ b/n8n/exposed/docker-compose-non-vuln.yml @@ -0,0 +1,19 @@ +services: + n8n: + image: n8nio/n8n:2.9.1 + container_name: n8n-non-vulnerable + restart: unless-stopped + + ports: + - "5678:5678" + + environment: + - N8N_HOST=0.0.0.0 + - N8N_PORT=5678 + - N8N_PROTOCOL=http + + volumes: + - n8n_data:/home/node/.n8n + +volumes: + n8n_data: diff --git a/n8n/exposed/docker-compose-vuln.yml b/n8n/exposed/docker-compose-vuln.yml new file mode 100644 index 00000000..3ef92b5f --- /dev/null +++ b/n8n/exposed/docker-compose-vuln.yml @@ -0,0 +1,19 @@ +services: + n8n: + image: n8nio/n8n:0.237.0 + container_name: n8n-vulnerable + restart: unless-stopped + + ports: + - "5678:5678" + + environment: + - N8N_HOST=0.0.0.0 + - N8N_PORT=5678 + - N8N_PROTOCOL=http + + volumes: + - n8n_data:/home/node/.n8n + +volumes: + n8n_data: From 63081d8d896945422878c4cbf858d87030939021 Mon Sep 17 00:00:00 2001 From: Yuriy Date: Fri, 27 Mar 2026 13:49:35 +0200 Subject: [PATCH 2/8] n8n testbeds updated --- n8n/exposed/README.md | 104 +++++++++++++++++-- n8n/exposed/docker-compose-non-vuln.yml | 14 +-- n8n/exposed/docker-compose-vuln-auth.yml | 11 ++ n8n/exposed/docker-compose-vuln-no-owner.yml | 7 ++ n8n/exposed/docker-compose-vuln.yml | 16 +-- 5 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 n8n/exposed/docker-compose-vuln-auth.yml create mode 100644 n8n/exposed/docker-compose-vuln-no-owner.yml diff --git a/n8n/exposed/README.md b/n8n/exposed/README.md index e8e23e30..8ee5f9a0 100644 --- a/n8n/exposed/README.md +++ b/n8n/exposed/README.md @@ -1,26 +1,112 @@ +Here's the corrected and tightened version: + +--- + # n8n Exposed REST API -n8n is a workflow automation platform that allows users to create and execute automated workflows. -This testbed demonstrates exposed and properly secured configurations of the n8n REST API. +n8n is a workflow automation platform for creating and executing automated workflows. This testbed demonstrates both exposed and properly secured configurations of the n8n REST API. + +## Vulnerable Setups + +```shell +docker compose -f docker-compose-vuln.yml up -d +``` +Early n8n versions (e.g. 0.54.0) shipped without user management. This setup reproduces that behavior: the REST API is fully accessible without authentication and the `/rest/login` endpoint does not exist. + +```shell +docker compose -f docker-compose-vuln-no-owner.yml up -d +``` +Later 0.x versions introduced user management but allowed instances to run without a configured owner account. In this state, the REST API is accessible without authentication. The `/rest/login` endpoint exists and returns a session cookie on anonymous requests, which can be used in subsequent calls as demonstrated in the steps below. + +```shell +docker compose -f docker-compose-vuln-auth.yml up -d +``` +Reproduces the BasicAuth split-brain misconfiguration: BasicAuth is enabled and protects the UI (returns `401`), but the `/rest/*` router has no auth middleware applied and remains fully accessible without credentials. -## Safe setup +## Safe Setup ```shell docker compose -f docker-compose-non-vuln.yml up -d ``` -This configuration enforces authentication and prevents unauthorized access to the REST API. +A properly configured n8n instance with authentication enforced on all `/rest/*` endpoints. The detector must produce no finding against this target. + +## Steps to Reproduce -## Vulnerable setup +### Step 1 — Fingerprint + +Confirm the target is n8n 0.x by checking for fields that are only present in the 0.x settings schema: ```shell -docker compose -f docker-compose-vuln.yml up -d +curl -s http://:5678/rest/settings | jq . ``` -This configuration exposes the REST API without authentication, allowing unauthorized access. + +Expected response on a vulnerable instance: + +```json +{ + "data": { + ... + "urlBaseWebhook": "http://:5678/", + "versionCli": "0.237.0", + "saveManualExecutions": false, + "saveDataErrorExecution": "all", + ... + } +} +``` + +The presence of `versionCli` and `urlBaseWebhook` confirms this is n8n 0.x. This fingerprint self-scoping — the detector cannot fire against modern instances regardless of their configuration. + +### Step 2 — Session Bootstrap (where applicable) + +On instances with user management enabled but no owner configured (`docker-compose-vuln-no-owner.yml`), an anonymous `GET /rest/login` returns a valid session cookie: + +```shell +curl -s -i http://:5678/rest/login +``` + +Expected response: + +``` +HTTP/1.1 200 OK +Set-Cookie: n8n-auth=; Path=/; HttpOnly; SameSite=Lax +``` + +On early versions without user management (`docker-compose-vuln.yml` and `docker-compose-vuln-auth.yml`), this endpoint does not exist. The cookie is not needed — `/rest/workflows` is directly accessible without one. + +### Step 3 — Workflows Access + +Without cookie (early versions / split-brain): + +```shell +curl -s http://:5678/rest/workflows | jq . +``` + +With cookie (no-owner setup): + +```shell +curl -s -H 'Cookie: n8n-auth=' http://:5678/rest/workflows | jq . +``` + +Expected response in both cases: + +```json +{ + "data": [ + { + ... + } + ] +} +``` + +Confirms unauthenticated (or pre-authentication anonymous session) access to workflow data. ## Notes -- In older versions, exposure may occur when authentication is disabled. -- In newer versions, this issue is often caused by misconfigurations such as improperly secured reverse proxies. +- The vulnerable surface spans all n8n 0.x releases. Early versions (e.g. 0.54.0) had no user management and optional auth that did not cover `/rest/*` routes. Later 0.x versions introduced user management but remained exploitable when no owner account was initialized, or when flags such as `N8N_USER_MANAGEMENT_DISABLED` produced inconsistent auth behavior. +- n8n 1.x enforces authentication at the application layer via session middleware applied directly to the `/rest/` router. Empirical testing confirms that `/rest/workflows` returns `401` on all 1.x+ configurations, including fresh instances with no owner account configured. Reverse proxy misconfigurations that forward unauthenticated requests still reach n8n's own auth check and do not bypass it. +- The `versionCli` and `urlBaseWebhook` fields in `/rest/settings` are unique to n8n 0.x. Their absence in 1.x+ means the fingerprint step will not match modern instances, preventing false positives by design. ## References diff --git a/n8n/exposed/docker-compose-non-vuln.yml b/n8n/exposed/docker-compose-non-vuln.yml index c52b4dca..303017a0 100644 --- a/n8n/exposed/docker-compose-non-vuln.yml +++ b/n8n/exposed/docker-compose-non-vuln.yml @@ -2,18 +2,6 @@ services: n8n: image: n8nio/n8n:2.9.1 container_name: n8n-non-vulnerable - restart: unless-stopped - ports: - "5678:5678" - - environment: - - N8N_HOST=0.0.0.0 - - N8N_PORT=5678 - - N8N_PROTOCOL=http - - volumes: - - n8n_data:/home/node/.n8n - -volumes: - n8n_data: + restart: unless-stopped diff --git a/n8n/exposed/docker-compose-vuln-auth.yml b/n8n/exposed/docker-compose-vuln-auth.yml new file mode 100644 index 00000000..bde34c25 --- /dev/null +++ b/n8n/exposed/docker-compose-vuln-auth.yml @@ -0,0 +1,11 @@ +services: + n8n: + image: n8nio/n8n:0.54.0 + container_name: n8n-vulnerable-auth + ports: + - "5678:5678" + environment: + - N8N_BASIC_AUTH_ACTIVE=true + - N8N_BASIC_AUTH_USER=admin + - N8N_BASIC_AUTH_PASSWORD=str0ngpassword! + restart: unless-stopped diff --git a/n8n/exposed/docker-compose-vuln-no-owner.yml b/n8n/exposed/docker-compose-vuln-no-owner.yml new file mode 100644 index 00000000..1b929f4c --- /dev/null +++ b/n8n/exposed/docker-compose-vuln-no-owner.yml @@ -0,0 +1,7 @@ +services: + n8n: + image: n8nio/n8n:0.237.0 + container_name: n8n-vulnerable-no-owner + ports: + - "5678:5678" + restart: unless-stopped diff --git a/n8n/exposed/docker-compose-vuln.yml b/n8n/exposed/docker-compose-vuln.yml index 3ef92b5f..aa20ce25 100644 --- a/n8n/exposed/docker-compose-vuln.yml +++ b/n8n/exposed/docker-compose-vuln.yml @@ -1,19 +1,7 @@ services: n8n: - image: n8nio/n8n:0.237.0 + image: n8nio/n8n:0.54.0 container_name: n8n-vulnerable - restart: unless-stopped - ports: - "5678:5678" - - environment: - - N8N_HOST=0.0.0.0 - - N8N_PORT=5678 - - N8N_PROTOCOL=http - - volumes: - - n8n_data:/home/node/.n8n - -volumes: - n8n_data: + restart: unless-stopped From 1a3f8a8c2bb57a3e31ba0996fe5a2ac427d9cbf6 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Wed, 8 Apr 2026 15:54:18 +0200 Subject: [PATCH 3/8] add: dpkg testbed --- osduplicate/dpkg/Dockerfile | 16 ++++++++++++ osduplicate/dpkg/README.md | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 osduplicate/dpkg/Dockerfile create mode 100644 osduplicate/dpkg/README.md diff --git a/osduplicate/dpkg/Dockerfile b/osduplicate/dpkg/Dockerfile new file mode 100644 index 00000000..d468a1b6 --- /dev/null +++ b/osduplicate/dpkg/Dockerfile @@ -0,0 +1,16 @@ +FROM debian:bookworm + +RUN apt update && apt-get install -y software-properties-common curl + +# install fzf since it is written in golang and is included in most of the +# package manager default repos +RUN apt install fzf + +RUN sh -c "echo 'deb [signed-by=/etc/apt/keyrings/dataproc.gpg] https://storage.googleapis.com/dataproc-bigtop-repo/2_3_deb12_20251203_064720-RC01 dataproc contrib' > /etc/apt/sources.list.d/dataproc.list" + +RUN sh -c "curl -s https://storage.googleapis.com/dataproc-bigtop-repo/2_3_deb12_20251203_064720-RC01/archive.key | gpg --dearmor -o /etc/apt/keyrings/dataproc.gpg" + +RUN apt-get update +RUN apt-get install -y spark-core + +CMD ["/bin/bash"] diff --git a/osduplicate/dpkg/README.md b/osduplicate/dpkg/README.md new file mode 100644 index 00000000..677c08b5 --- /dev/null +++ b/osduplicate/dpkg/README.md @@ -0,0 +1,50 @@ +# DPKG OS Testbed + +This testbed is developed to verify that scalibr adds the exploitability signals (related to `osduplicate`) only to packages which were added through default repositories. + +Inventories returned from files which are part of `dpkg` packages installed via default repository receive an OS-level advisory which is more precise and less prone to false positives, while packages installed by other means do not receive any OS-level advisory and should be covered using language-level extractors. + +In this example the `Dockerfile` installs: + +- `fzf` from a default repository +- `spark-core` from a custom repository + +## Verification Steps + +Build the image and run the testbed: + +```sh +docker build --platform linux/amd64 -t osduplicate-dpkg . +# mount scalibr into the container +docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-dpkg +``` + +Verify that fzf was installed via a main repository while spark-core was installed from a custom one: + +```sh +apt-cache policy fzf +apt-cache policy spark-core +``` + +Verify that fzf and spark-core are both detected by their respective language extractor: + +```sh +scalibr -plugins="java/archive,go/binary" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -C 10 'usr/bin/fzf' +cat /tmp/result.textproto | grep -C 10 'usr/lib/spark/jars/spark-core_2.12-3.5.3.jar' +``` + +Verify that fzf and spark-core are both detected by the "os/dpkg" extractor: + +```sh +scalibr -plugins="os/dpkg" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -C 10 '\sname: "fzf"' +cat /tmp/result.textproto | grep -C 10 '\sname: "spark-core"' +``` + +Verify that exploitability_signals are only added to fzf related packages: + +```sh +/tmp/scalibr -plugins="vex/os-duplicate/dpkg,os/dpkg,java/archive,go/binary" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -C 10 'exploitability_signals' +``` From cadb4e17ef02fb4468bd1bd331a4305b89a280dc Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 10 Apr 2026 00:06:16 +0200 Subject: [PATCH 4/8] add: apk testbed --- osduplicate/apk/Dockerfile | 13 ++++++++++ osduplicate/apk/README.md | 50 ++++++++++++++++++++++++++++++++++++++ osduplicate/dpkg/README.md | 2 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 osduplicate/apk/Dockerfile create mode 100644 osduplicate/apk/README.md diff --git a/osduplicate/apk/Dockerfile b/osduplicate/apk/Dockerfile new file mode 100644 index 00000000..fa19b723 --- /dev/null +++ b/osduplicate/apk/Dockerfile @@ -0,0 +1,13 @@ +FROM alpine:3.21 + +RUN apk add fzf + +# 1. Grab their public key +RUN wget -O /etc/apk/keys/packages@orb.net.rsa.pub https://pkgs.orb.net/stable/alpine/orb.pub + +# 2. Add their repo URL +RUN echo "https://pkgs.orb.net/stable/alpine" >> /etc/apk/repositories + +# 3. Update and install +RUN apk update +RUN apk add orb diff --git a/osduplicate/apk/README.md b/osduplicate/apk/README.md new file mode 100644 index 00000000..09208d61 --- /dev/null +++ b/osduplicate/apk/README.md @@ -0,0 +1,50 @@ +# APK OS Testbed + +This testbed is developed to verify that scalibr adds the exploitability signals (related to `osduplicate`) only to packages which were added through default repositories. + +Inventories returned from files which are part of `apk` packages installed via default repository receive an OS-level advisory which is more precise and less prone to false positives, while packages installed by other means do not receive any OS-level advisory and should be covered using language-level extractors. + +In this example the `Dockerfile` installs: + +- `fzf` from a default repository +- `orb` from a custom repository + +## Verification Steps + +Build the image and run the testbed: + +```sh +docker build --platform linux/amd64 -t osduplicate-apk . +# mount scalibr into the container +docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-apk +``` + +Verify that fzf was installed via a main repository while orb was installed from a custom one: + +```sh +apk policy fzf +apk policy orb +``` + +Verify that fzf and orb are both detected: + +```sh +scalibr -plugins="go/binary" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -C 10 'usr/bin/fzf' +cat /tmp/result.textproto | grep -C 10 'usr/bin/orb' +``` + +Verify that fzf and orb are both detected by the "os/apk" extractor: + +```sh +scalibr -plugins="os/apk" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -C 10 '\sname: "fzf"' +cat /tmp/result.textproto | grep -C 10 '\sname: "orb"' +``` + +Verify that exploitability_signals are only added to fzf related packages: + +```sh +scalibr -plugins="vex/os-duplicate/apk,os/apk,go/binary" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -C 10 'exploitability_signals' +``` diff --git a/osduplicate/dpkg/README.md b/osduplicate/dpkg/README.md index 677c08b5..5a940ec9 100644 --- a/osduplicate/dpkg/README.md +++ b/osduplicate/dpkg/README.md @@ -45,6 +45,6 @@ cat /tmp/result.textproto | grep -C 10 '\sname: "spark-core"' Verify that exploitability_signals are only added to fzf related packages: ```sh -/tmp/scalibr -plugins="vex/os-duplicate/dpkg,os/dpkg,java/archive,go/binary" -o textproto=/tmp/result.textproto +scalibr -plugins="vex/os-duplicate/dpkg,os/dpkg,java/archive,go/binary" -o textproto=/tmp/result.textproto cat /tmp/result.textproto | grep -C 10 'exploitability_signals' ``` From 4bd95de3eb8f8ea1d80e0959bde1ced2d8420f0f Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Mon, 13 Apr 2026 10:45:58 +0200 Subject: [PATCH 5/8] add: rpm testbeds --- osduplicate/apk/Dockerfile | 23 ++++-- osduplicate/apk/README.md | 32 +------- osduplicate/apk/verify.sh | 25 ++++++ osduplicate/dpkg/Dockerfile | 23 ++++-- osduplicate/dpkg/README.md | 32 +------- osduplicate/dpkg/verify.sh | 25 ++++++ osduplicate/rpm/README.md | 15 ++++ osduplicate/rpm/docker-compose.yml | 126 +++++++++++++++++++++++++++++ osduplicate/rpm/verify.sh | 25 ++++++ 9 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 osduplicate/apk/verify.sh create mode 100644 osduplicate/dpkg/verify.sh create mode 100644 osduplicate/rpm/README.md create mode 100644 osduplicate/rpm/docker-compose.yml create mode 100644 osduplicate/rpm/verify.sh diff --git a/osduplicate/apk/Dockerfile b/osduplicate/apk/Dockerfile index fa19b723..ddaa52f0 100644 --- a/osduplicate/apk/Dockerfile +++ b/osduplicate/apk/Dockerfile @@ -1,13 +1,26 @@ FROM alpine:3.21 +# Install a pkg from main repos RUN apk add fzf -# 1. Grab their public key -RUN wget -O /etc/apk/keys/packages@orb.net.rsa.pub https://pkgs.orb.net/stable/alpine/orb.pub +# Verify that the extra pkg is not available +RUN ! apk add --simulate orb -# 2. Add their repo URL +# Add external repository +RUN wget -O /etc/apk/keys/packages@orb.net.rsa.pub https://pkgs.orb.net/stable/alpine/orb.pub RUN echo "https://pkgs.orb.net/stable/alpine" >> /etc/apk/repositories - -# 3. Update and install RUN apk update + +# Install extra pkg RUN apk add orb + +COPY verify.sh /usr/bin/verify.sh +RUN chmod +x /usr/bin/verify.sh + +ENV DEFAULT_PKG="fzf" +ENV DEFAULT_PKG_FILE="usr/bin/fzf" +ENV EXTRA_PKG="orb" +ENV EXTRA_PKG_FILE="usr/bin/orb" +ENV LANGUAGE_EXTRACTORS="go/binary" +ENV OS_EXTRACTOR="os/apk" +ENV DUP_ANNOTATOR="vex/os-duplicate/apk" diff --git a/osduplicate/apk/README.md b/osduplicate/apk/README.md index 09208d61..90f505e9 100644 --- a/osduplicate/apk/README.md +++ b/osduplicate/apk/README.md @@ -15,36 +15,12 @@ Build the image and run the testbed: ```sh docker build --platform linux/amd64 -t osduplicate-apk . -# mount scalibr into the container -docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-apk -``` - -Verify that fzf was installed via a main repository while orb was installed from a custom one: - -```sh -apk policy fzf -apk policy orb +docker run --rm --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-apk /usr/bin/verify.sh ``` -Verify that fzf and orb are both detected: +For manual verification launch: ```sh -scalibr -plugins="go/binary" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -C 10 'usr/bin/fzf' -cat /tmp/result.textproto | grep -C 10 'usr/bin/orb' -``` - -Verify that fzf and orb are both detected by the "os/apk" extractor: - -```sh -scalibr -plugins="os/apk" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -C 10 '\sname: "fzf"' -cat /tmp/result.textproto | grep -C 10 '\sname: "orb"' -``` - -Verify that exploitability_signals are only added to fzf related packages: - -```sh -scalibr -plugins="vex/os-duplicate/apk,os/apk,go/binary" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -C 10 'exploitability_signals' +docker build --platform linux/amd64 -t osduplicate-apk . +docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-apk ``` diff --git a/osduplicate/apk/verify.sh b/osduplicate/apk/verify.sh new file mode 100644 index 00000000..f6ba590f --- /dev/null +++ b/osduplicate/apk/verify.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +set -e + +# Verify Language Extractors +scalibr -plugins="$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto +grep -q "$DEFAULT_PKG_FILE" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE not found"; exit 1; } +grep -q "$EXTRA_PKG_FILE" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG_FILE: $EXTRA_PKG_FILE not found"; exit 1; } + +echo "----" + +# Verify OS Extractor +scalibr -plugins="$OS_EXTRACTOR" -o textproto=/tmp/result.textproto +grep -qE "\sname:\s+\"$DEFAULT_PKG\"" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG: $DEFAULT_PKG not detected by OS extractor"; exit 1; } +grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG: $EXTRA_PKG not detected by OS extractor"; exit 1; } + +echo "----" + +# Verify that exploitability_signals are only added to default pkg related packages: + +scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -qv "$EXTRA_PKG_FILE" || { echo "FAIL: exploitability_signals added to inventory found inside the EXTRA_PKG_FILE: $EXTRA_PKG_FILE"; exit 1; } +cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -q "$DEFAULT_PKG_FILE" || { echo "FAIL: no exploitability_signals added to inventory found inside the DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE"; exit 1; } + +echo "--- All Tests Passed ---" diff --git a/osduplicate/dpkg/Dockerfile b/osduplicate/dpkg/Dockerfile index d468a1b6..b0f4ce68 100644 --- a/osduplicate/dpkg/Dockerfile +++ b/osduplicate/dpkg/Dockerfile @@ -2,15 +2,28 @@ FROM debian:bookworm RUN apt update && apt-get install -y software-properties-common curl -# install fzf since it is written in golang and is included in most of the -# package manager default repos +# Verify that spark-core is not available in default repos +RUN ! apt install spark-core + +# Install fzf from the default repos RUN apt install fzf +# Add extra repository RUN sh -c "echo 'deb [signed-by=/etc/apt/keyrings/dataproc.gpg] https://storage.googleapis.com/dataproc-bigtop-repo/2_3_deb12_20251203_064720-RC01 dataproc contrib' > /etc/apt/sources.list.d/dataproc.list" - RUN sh -c "curl -s https://storage.googleapis.com/dataproc-bigtop-repo/2_3_deb12_20251203_064720-RC01/archive.key | gpg --dearmor -o /etc/apt/keyrings/dataproc.gpg" - RUN apt-get update + +# Add extra pkg RUN apt-get install -y spark-core -CMD ["/bin/bash"] + +COPY verify.sh /usr/bin/verify.sh +RUN chmod +x /usr/bin/verify.sh + +ENV DEFAULT_PKG="fzf" +ENV DEFAULT_PKG_FILE="usr/bin/fzf" +ENV EXTRA_PKG="spark-core" +ENV EXTRA_PKG_FILE="usr/lib/spark/jars/spark-core_2.12-3.5.3.jar" +ENV LANGUAGE_EXTRACTORS="go/binary,java/archive" +ENV OS_EXTRACTOR="os/dpkg" +ENV DUP_ANNOTATOR="vex/os-duplicate/dpkg" diff --git a/osduplicate/dpkg/README.md b/osduplicate/dpkg/README.md index 5a940ec9..58b5189d 100644 --- a/osduplicate/dpkg/README.md +++ b/osduplicate/dpkg/README.md @@ -15,36 +15,12 @@ Build the image and run the testbed: ```sh docker build --platform linux/amd64 -t osduplicate-dpkg . -# mount scalibr into the container -docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-dpkg -``` - -Verify that fzf was installed via a main repository while spark-core was installed from a custom one: - -```sh -apt-cache policy fzf -apt-cache policy spark-core +docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-dpkg /usr/bin/verify.sh ``` -Verify that fzf and spark-core are both detected by their respective language extractor: +For manual verification launch: ```sh -scalibr -plugins="java/archive,go/binary" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -C 10 'usr/bin/fzf' -cat /tmp/result.textproto | grep -C 10 'usr/lib/spark/jars/spark-core_2.12-3.5.3.jar' -``` - -Verify that fzf and spark-core are both detected by the "os/dpkg" extractor: - -```sh -scalibr -plugins="os/dpkg" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -C 10 '\sname: "fzf"' -cat /tmp/result.textproto | grep -C 10 '\sname: "spark-core"' -``` - -Verify that exploitability_signals are only added to fzf related packages: - -```sh -scalibr -plugins="vex/os-duplicate/dpkg,os/dpkg,java/archive,go/binary" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -C 10 'exploitability_signals' +docker build --platform linux/amd64 -t osduplicate-dpkg . +docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-dpkg ``` diff --git a/osduplicate/dpkg/verify.sh b/osduplicate/dpkg/verify.sh new file mode 100644 index 00000000..f6ba590f --- /dev/null +++ b/osduplicate/dpkg/verify.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +set -e + +# Verify Language Extractors +scalibr -plugins="$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto +grep -q "$DEFAULT_PKG_FILE" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE not found"; exit 1; } +grep -q "$EXTRA_PKG_FILE" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG_FILE: $EXTRA_PKG_FILE not found"; exit 1; } + +echo "----" + +# Verify OS Extractor +scalibr -plugins="$OS_EXTRACTOR" -o textproto=/tmp/result.textproto +grep -qE "\sname:\s+\"$DEFAULT_PKG\"" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG: $DEFAULT_PKG not detected by OS extractor"; exit 1; } +grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG: $EXTRA_PKG not detected by OS extractor"; exit 1; } + +echo "----" + +# Verify that exploitability_signals are only added to default pkg related packages: + +scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -qv "$EXTRA_PKG_FILE" || { echo "FAIL: exploitability_signals added to inventory found inside the EXTRA_PKG_FILE: $EXTRA_PKG_FILE"; exit 1; } +cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -q "$DEFAULT_PKG_FILE" || { echo "FAIL: no exploitability_signals added to inventory found inside the DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE"; exit 1; } + +echo "--- All Tests Passed ---" diff --git a/osduplicate/rpm/README.md b/osduplicate/rpm/README.md new file mode 100644 index 00000000..ebe5bdd8 --- /dev/null +++ b/osduplicate/rpm/README.md @@ -0,0 +1,15 @@ +# RPM OS Testbed + +This testbed is developed to verify that scalibr adds the exploitability signals (related to `osduplicate`) only to packages which were added through default repositories. + +Inventories returned from files which are part of `rpm` packages installed via default repository receive an OS-level advisory which is more precise and less prone to false positives, while packages installed by other means do not receive any OS-level advisory and should be covered using language-level extractors. + +The docker-compose contains multiple docker images which install a pkg from a default repository and from one from an extra one. Then the `verify.sh` script is used to ensure that scalibr correctly adds `exploitability_signals` only to the pkg installed via the default repo. + +## Verification Steps + +Build the image and run the testbed: + +```sh +docker compose build +``` diff --git a/osduplicate/rpm/docker-compose.yml b/osduplicate/rpm/docker-compose.yml new file mode 100644 index 00000000..aa83a702 --- /dev/null +++ b/osduplicate/rpm/docker-compose.yml @@ -0,0 +1,126 @@ +x-common-ven: &common + volumes: + - ./../scalibr:/usr/bin/scalibr + command: ["/bin/sh", "-c", "/usr/bin/verify.sh || cat /tmp/result.textproto"] + environment: + - DEFAULT_PKG=runc + - DEFAULT_PKG_FILE=usr/bin/runc + - EXTRA_PKG=gh + - EXTRA_PKG_FILE=usr/bin/gh + - LANGUAGE_EXTRACTORS=go/binary + - OS_EXTRACTOR=os/rpm + - DUP_ANNOTATOR=vex/os-duplicate/rpm + +services: + rhel: + <<: *common + image: osduplicate-rpm-rhel + build: + context: . + dockerfile_inline: | + FROM registry.access.redhat.com/ubi9/ubi:latest + RUN dnf install -y runc + RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + + centos: + <<: *common + image: osduplicate-rpm-centos + build: + context: . + dockerfile_inline: | + FROM quay.io/centos/centos:stream9 + RUN dnf install -y runc + RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + + almalinux: + <<: *common + image: osduplicate-rpm-almalinux + build: + context: . + dockerfile_inline: | + FROM almalinux:9 + RUN dnf install -y runc + RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + + rockylinux: + <<: *common + image: osduplicate-rpm-rockylinux + build: + context: . + dockerfile_inline: | + FROM rockylinux:9 + RUN dnf install -y runc + RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + + sles: + <<: *common + image: osduplicate-rpm-sles + build: + context: . + dockerfile_inline: | + FROM registry.suse.com/bci/bci-base:15.6 + RUN zypper install -y runc + RUN zypper addrepo https://cli.github.com/packages/rpm/gh-cli.repo && \ + zypper --gpg-auto-import-keys refresh && \ + zypper install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + + opensuse: + <<: *common + image: osduplicate-rpm-opensuse + build: + context: . + dockerfile_inline: | + FROM opensuse/leap:15.6 + RUN zypper install -y runc + RUN zypper addrepo https://cli.github.com/packages/rpm/gh-cli.repo && \ + zypper --gpg-auto-import-keys refresh && \ + zypper install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + environment: + - DEFAULT_PKG_FILE=usr/bin/runc + + amazonlinux2: + <<: *common + image: osduplicate-rpm-amazonlinux2 + build: + context: . + dockerfile_inline: | + FROM amazonlinux:2 + RUN yum install -y runc yum-utils + RUN yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + yum install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh + + amazonlinux2023: + <<: *common + image: osduplicate-rpm-amazonlinux2023 + build: + context: . + dockerfile_inline: | + FROM amazonlinux:2023 + RUN dnf install -y runc + RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh + COPY verify.sh /usr/bin/verify.sh + RUN chmod +x /usr/bin/verify.sh diff --git a/osduplicate/rpm/verify.sh b/osduplicate/rpm/verify.sh new file mode 100644 index 00000000..f6ba590f --- /dev/null +++ b/osduplicate/rpm/verify.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +set -e + +# Verify Language Extractors +scalibr -plugins="$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto +grep -q "$DEFAULT_PKG_FILE" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE not found"; exit 1; } +grep -q "$EXTRA_PKG_FILE" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG_FILE: $EXTRA_PKG_FILE not found"; exit 1; } + +echo "----" + +# Verify OS Extractor +scalibr -plugins="$OS_EXTRACTOR" -o textproto=/tmp/result.textproto +grep -qE "\sname:\s+\"$DEFAULT_PKG\"" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG: $DEFAULT_PKG not detected by OS extractor"; exit 1; } +grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG: $EXTRA_PKG not detected by OS extractor"; exit 1; } + +echo "----" + +# Verify that exploitability_signals are only added to default pkg related packages: + +scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto +cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -qv "$EXTRA_PKG_FILE" || { echo "FAIL: exploitability_signals added to inventory found inside the EXTRA_PKG_FILE: $EXTRA_PKG_FILE"; exit 1; } +cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -q "$DEFAULT_PKG_FILE" || { echo "FAIL: no exploitability_signals added to inventory found inside the DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE"; exit 1; } + +echo "--- All Tests Passed ---" From fd873900375599815fef1cfdadd7b1b042b48ded Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Mon, 13 Apr 2026 13:19:28 +0200 Subject: [PATCH 6/8] fix: use more resilient check with awk --- osduplicate/apk/verify.sh | 27 +++++++++++++++++++-- osduplicate/dpkg/verify.sh | 27 +++++++++++++++++++-- osduplicate/rpm/common.env | 7 ++++++ osduplicate/rpm/docker-compose.yml | 36 +++++++++++++++------------- osduplicate/rpm/verify.sh | 38 ++++++++++++++++++++++++++++-- 5 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 osduplicate/rpm/common.env diff --git a/osduplicate/apk/verify.sh b/osduplicate/apk/verify.sh index f6ba590f..5a84082a 100644 --- a/osduplicate/apk/verify.sh +++ b/osduplicate/apk/verify.sh @@ -16,10 +16,33 @@ grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA echo "----" +has_exploitability_signals() { + local target="$1" + + awk -v tgt="$target" ' + # We escape the { to treat it as a literal character + BEGIN { RS = "packages:[[:space:]]*\\{" } + + $0 ~ tgt && /exploitability_signals/ { + found = 1; + exit 0 + } + END { exit (found ? 0 : 1) } + ' /tmp/result.textproto +} + + # Verify that exploitability_signals are only added to default pkg related packages: scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -qv "$EXTRA_PKG_FILE" || { echo "FAIL: exploitability_signals added to inventory found inside the EXTRA_PKG_FILE: $EXTRA_PKG_FILE"; exit 1; } -cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -q "$DEFAULT_PKG_FILE" || { echo "FAIL: no exploitability_signals added to inventory found inside the DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE"; exit 1; } +if ! has_exploitability_signals "$DEFAULT_PKG_FILE"; then + echo "FAIL: exploitability_signals missing from $DEFAULT_PKG_FILE" >&2 + exit 1 +fi +if has_exploitability_signals "$EXTRA_PKG_FILE"; then + echo "FAIL: exploitability_signals unexpectedly found in $EXTRA_PKG_FILE" >&2 + exit 1 +fi + echo "--- All Tests Passed ---" diff --git a/osduplicate/dpkg/verify.sh b/osduplicate/dpkg/verify.sh index f6ba590f..5a84082a 100644 --- a/osduplicate/dpkg/verify.sh +++ b/osduplicate/dpkg/verify.sh @@ -16,10 +16,33 @@ grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA echo "----" +has_exploitability_signals() { + local target="$1" + + awk -v tgt="$target" ' + # We escape the { to treat it as a literal character + BEGIN { RS = "packages:[[:space:]]*\\{" } + + $0 ~ tgt && /exploitability_signals/ { + found = 1; + exit 0 + } + END { exit (found ? 0 : 1) } + ' /tmp/result.textproto +} + + # Verify that exploitability_signals are only added to default pkg related packages: scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -qv "$EXTRA_PKG_FILE" || { echo "FAIL: exploitability_signals added to inventory found inside the EXTRA_PKG_FILE: $EXTRA_PKG_FILE"; exit 1; } -cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -q "$DEFAULT_PKG_FILE" || { echo "FAIL: no exploitability_signals added to inventory found inside the DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE"; exit 1; } +if ! has_exploitability_signals "$DEFAULT_PKG_FILE"; then + echo "FAIL: exploitability_signals missing from $DEFAULT_PKG_FILE" >&2 + exit 1 +fi +if has_exploitability_signals "$EXTRA_PKG_FILE"; then + echo "FAIL: exploitability_signals unexpectedly found in $EXTRA_PKG_FILE" >&2 + exit 1 +fi + echo "--- All Tests Passed ---" diff --git a/osduplicate/rpm/common.env b/osduplicate/rpm/common.env new file mode 100644 index 00000000..082d5c18 --- /dev/null +++ b/osduplicate/rpm/common.env @@ -0,0 +1,7 @@ +DEFAULT_PKG=runc +DEFAULT_PKG_FILE=usr/bin/runc +EXTRA_PKG=gh +EXTRA_PKG_FILE=usr/bin/gh +LANGUAGE_EXTRACTORS=go/binary +OS_EXTRACTOR=os/rpm +DUP_ANNOTATOR=vex/os-duplicate/rpm diff --git a/osduplicate/rpm/docker-compose.yml b/osduplicate/rpm/docker-compose.yml index aa83a702..2430aeb9 100644 --- a/osduplicate/rpm/docker-compose.yml +++ b/osduplicate/rpm/docker-compose.yml @@ -1,15 +1,9 @@ -x-common-ven: &common +x-common: &common volumes: - ./../scalibr:/usr/bin/scalibr - command: ["/bin/sh", "-c", "/usr/bin/verify.sh || cat /tmp/result.textproto"] - environment: - - DEFAULT_PKG=runc - - DEFAULT_PKG_FILE=usr/bin/runc - - EXTRA_PKG=gh - - EXTRA_PKG_FILE=usr/bin/gh - - LANGUAGE_EXTRACTORS=go/binary - - OS_EXTRACTOR=os/rpm - - DUP_ANNOTATOR=vex/os-duplicate/rpm + command: ["/bin/sh", "-c", "/usr/bin/verify.sh || sleep infinity"] + env_file: + - common.env services: rhel: @@ -75,12 +69,14 @@ services: context: . dockerfile_inline: | FROM registry.suse.com/bci/bci-base:15.6 - RUN zypper install -y runc + RUN zypper install -y runc awk RUN zypper addrepo https://cli.github.com/packages/rpm/gh-cli.repo && \ zypper --gpg-auto-import-keys refresh && \ zypper install -y gh COPY verify.sh /usr/bin/verify.sh RUN chmod +x /usr/bin/verify.sh + environment: + - DEFAULT_PKG_FILE=usr/sbin/runc opensuse: <<: *common @@ -95,8 +91,8 @@ services: zypper install -y gh COPY verify.sh /usr/bin/verify.sh RUN chmod +x /usr/bin/verify.sh - environment: - - DEFAULT_PKG_FILE=usr/bin/runc + environment: + - DEFAULT_PKG_FILE=usr/sbin/runc amazonlinux2: <<: *common @@ -105,11 +101,17 @@ services: context: . dockerfile_inline: | FROM amazonlinux:2 - RUN yum install -y runc yum-utils - RUN yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ - yum install -y gh + RUN yum install -y tomcat + RUN amazon-linux-extras enable docker && \ + yum install -y runc COPY verify.sh /usr/bin/verify.sh RUN chmod +x /usr/bin/verify.sh + environment: + - DEFAULT_PKG=tomcat + - DEFAULT_PKG_FILE=usr/share/java/avalon-framework-api.jar + - EXTRA_PKG=runc + - EXTRA_PKG_FILE=usr/sbin/runc + - LANGUAGE_EXTRACTORS=go/binary,java/archive amazonlinux2023: <<: *common @@ -124,3 +126,5 @@ services: dnf install -y gh COPY verify.sh /usr/bin/verify.sh RUN chmod +x /usr/bin/verify.sh + environment: + - DEFAULT_PKG_FILE=usr/sbin/runc diff --git a/osduplicate/rpm/verify.sh b/osduplicate/rpm/verify.sh index f6ba590f..51c43f70 100644 --- a/osduplicate/rpm/verify.sh +++ b/osduplicate/rpm/verify.sh @@ -16,10 +16,44 @@ grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA echo "----" +has_exploitability_signals() { + local target="$1" + local file=/tmp/result.textproto + + # Fast-fail: If the target isn't in the file at all, return 1 (false) + if ! grep -q "$target" "$file"; then + return 1 + fi + + # Use awk to check the packages content: + # - treat `packages: {` as separator + # - check if the package block ($0) contains both the target file and exploitability_signals + # - if so return 0 (true) + # - if no match is found by the end return 1 (false) + awk -v tgt="$target" ' + # We escape the { to treat it as a literal character + BEGIN { RS = "packages:[[:space:]]*\\{" } + + $0 ~ tgt && /exploitability_signals/ { + found = 1; + exit 0 + } + END { exit (found ? 0 : 1) } + ' $file +} + + # Verify that exploitability_signals are only added to default pkg related packages: scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -qv "$EXTRA_PKG_FILE" || { echo "FAIL: exploitability_signals added to inventory found inside the EXTRA_PKG_FILE: $EXTRA_PKG_FILE"; exit 1; } -cat /tmp/result.textproto | grep -B 5 'exploitability_signals' | grep -q "$DEFAULT_PKG_FILE" || { echo "FAIL: no exploitability_signals added to inventory found inside the DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE"; exit 1; } +if ! has_exploitability_signals "$DEFAULT_PKG_FILE"; then + echo "FAIL: exploitability_signals missing from $DEFAULT_PKG_FILE" >&2 + exit 1 +fi +if has_exploitability_signals "$EXTRA_PKG_FILE"; then + echo "FAIL: exploitability_signals unexpectedly found in $EXTRA_PKG_FILE" >&2 + exit 1 +fi + echo "--- All Tests Passed ---" From 0443c6e8a36a9b2b76016b977ad1e09a5d96deb5 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Mon, 13 Apr 2026 13:29:59 +0200 Subject: [PATCH 7/8] add: manual verification instructions --- osduplicate/rpm/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osduplicate/rpm/README.md b/osduplicate/rpm/README.md index ebe5bdd8..9c3ad7d6 100644 --- a/osduplicate/rpm/README.md +++ b/osduplicate/rpm/README.md @@ -11,5 +11,11 @@ The docker-compose contains multiple docker images which install a pkg from a de Build the image and run the testbed: ```sh -docker compose build +docker compose up --build +``` + +For manual verification launch: + +```sh +docker compose run amazonlinux2 bash ``` From e97ead4d8241641fa8938f9e87117cb70ec4bde6 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 16 Apr 2026 12:47:40 +0200 Subject: [PATCH 8/8] edit: move the verify.sh file to the top level folder --- osduplicate/README.md | 3 ++ osduplicate/apk/README.md | 8 ++--- osduplicate/apk/verify.sh | 48 ------------------------------ osduplicate/dpkg/README.md | 8 ++--- osduplicate/dpkg/verify.sh | 48 ------------------------------ osduplicate/rpm/docker-compose.yml | 18 +++++------ osduplicate/{rpm => }/verify.sh | 0 7 files changed, 20 insertions(+), 113 deletions(-) create mode 100644 osduplicate/README.md delete mode 100644 osduplicate/apk/verify.sh delete mode 100644 osduplicate/dpkg/verify.sh rename osduplicate/{rpm => }/verify.sh (100%) diff --git a/osduplicate/README.md b/osduplicate/README.md new file mode 100644 index 00000000..f5b19a2f --- /dev/null +++ b/osduplicate/README.md @@ -0,0 +1,3 @@ +# OS duplicate testbeds + +This folder contains testbeds for validating scalibr `osduplicate` Annotators diff --git a/osduplicate/apk/README.md b/osduplicate/apk/README.md index 90f505e9..e7768e70 100644 --- a/osduplicate/apk/README.md +++ b/osduplicate/apk/README.md @@ -14,13 +14,13 @@ In this example the `Dockerfile` installs: Build the image and run the testbed: ```sh -docker build --platform linux/amd64 -t osduplicate-apk . -docker run --rm --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-apk /usr/bin/verify.sh +docker build --platform linux/amd64 -t osduplicate-apk -f Dockerfile .. +docker run --rm --platform linux/amd64 -v /tmp/scalibr:/usr/bin/scalibr osduplicate-apk /usr/bin/verify.sh ``` For manual verification launch: ```sh -docker build --platform linux/amd64 -t osduplicate-apk . -docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-apk +docker build --platform linux/amd64 -t osduplicate-apk . -f Dockerfile .. +docker run --rm -it --platform linux/amd64 -v /tmp/scalibr:/usr/bin/scalibr osduplicate-apk ``` diff --git a/osduplicate/apk/verify.sh b/osduplicate/apk/verify.sh deleted file mode 100644 index 5a84082a..00000000 --- a/osduplicate/apk/verify.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env sh - -set -e - -# Verify Language Extractors -scalibr -plugins="$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -grep -q "$DEFAULT_PKG_FILE" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE not found"; exit 1; } -grep -q "$EXTRA_PKG_FILE" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG_FILE: $EXTRA_PKG_FILE not found"; exit 1; } - -echo "----" - -# Verify OS Extractor -scalibr -plugins="$OS_EXTRACTOR" -o textproto=/tmp/result.textproto -grep -qE "\sname:\s+\"$DEFAULT_PKG\"" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG: $DEFAULT_PKG not detected by OS extractor"; exit 1; } -grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG: $EXTRA_PKG not detected by OS extractor"; exit 1; } - -echo "----" - -has_exploitability_signals() { - local target="$1" - - awk -v tgt="$target" ' - # We escape the { to treat it as a literal character - BEGIN { RS = "packages:[[:space:]]*\\{" } - - $0 ~ tgt && /exploitability_signals/ { - found = 1; - exit 0 - } - END { exit (found ? 0 : 1) } - ' /tmp/result.textproto -} - - -# Verify that exploitability_signals are only added to default pkg related packages: - -scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -if ! has_exploitability_signals "$DEFAULT_PKG_FILE"; then - echo "FAIL: exploitability_signals missing from $DEFAULT_PKG_FILE" >&2 - exit 1 -fi -if has_exploitability_signals "$EXTRA_PKG_FILE"; then - echo "FAIL: exploitability_signals unexpectedly found in $EXTRA_PKG_FILE" >&2 - exit 1 -fi - - -echo "--- All Tests Passed ---" diff --git a/osduplicate/dpkg/README.md b/osduplicate/dpkg/README.md index 58b5189d..ef6ec498 100644 --- a/osduplicate/dpkg/README.md +++ b/osduplicate/dpkg/README.md @@ -14,13 +14,13 @@ In this example the `Dockerfile` installs: Build the image and run the testbed: ```sh -docker build --platform linux/amd64 -t osduplicate-dpkg . -docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-dpkg /usr/bin/verify.sh +docker build --platform linux/amd64 -t osduplicate-dpkg -f Dockerfile .. +docker run --rm -it --platform linux/amd64 -v /tmp/scalibr:/usr/bin/scalibr osduplicate-dpkg /usr/bin/verify.sh ``` For manual verification launch: ```sh -docker build --platform linux/amd64 -t osduplicate-dpkg . -docker run --rm -it --platform linux/amd64 -v ./../scalibr:/usr/bin/scalibr osduplicate-dpkg +docker build --platform linux/amd64 -t osduplicate-dpkg -f Dockerfile .. +docker run --rm -it --platform linux/amd64 -v /tmp/scalibr:/usr/bin/scalibr osduplicate-dpkg ``` diff --git a/osduplicate/dpkg/verify.sh b/osduplicate/dpkg/verify.sh deleted file mode 100644 index 5a84082a..00000000 --- a/osduplicate/dpkg/verify.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env sh - -set -e - -# Verify Language Extractors -scalibr -plugins="$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -grep -q "$DEFAULT_PKG_FILE" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG_FILE: $DEFAULT_PKG_FILE not found"; exit 1; } -grep -q "$EXTRA_PKG_FILE" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG_FILE: $EXTRA_PKG_FILE not found"; exit 1; } - -echo "----" - -# Verify OS Extractor -scalibr -plugins="$OS_EXTRACTOR" -o textproto=/tmp/result.textproto -grep -qE "\sname:\s+\"$DEFAULT_PKG\"" /tmp/result.textproto || { echo "FAIL: DEFAULT_PKG: $DEFAULT_PKG not detected by OS extractor"; exit 1; } -grep -qE "\sname:\s+\"$EXTRA_PKG\"" /tmp/result.textproto || { echo "FAIL: EXTRA_PKG: $EXTRA_PKG not detected by OS extractor"; exit 1; } - -echo "----" - -has_exploitability_signals() { - local target="$1" - - awk -v tgt="$target" ' - # We escape the { to treat it as a literal character - BEGIN { RS = "packages:[[:space:]]*\\{" } - - $0 ~ tgt && /exploitability_signals/ { - found = 1; - exit 0 - } - END { exit (found ? 0 : 1) } - ' /tmp/result.textproto -} - - -# Verify that exploitability_signals are only added to default pkg related packages: - -scalibr -plugins="$OS_EXTRACTOR,$DUP_ANNOTATOR,$LANGUAGE_EXTRACTORS" -o textproto=/tmp/result.textproto -if ! has_exploitability_signals "$DEFAULT_PKG_FILE"; then - echo "FAIL: exploitability_signals missing from $DEFAULT_PKG_FILE" >&2 - exit 1 -fi -if has_exploitability_signals "$EXTRA_PKG_FILE"; then - echo "FAIL: exploitability_signals unexpectedly found in $EXTRA_PKG_FILE" >&2 - exit 1 -fi - - -echo "--- All Tests Passed ---" diff --git a/osduplicate/rpm/docker-compose.yml b/osduplicate/rpm/docker-compose.yml index 2430aeb9..8a5e681b 100644 --- a/osduplicate/rpm/docker-compose.yml +++ b/osduplicate/rpm/docker-compose.yml @@ -1,6 +1,6 @@ x-common: &common volumes: - - ./../scalibr:/usr/bin/scalibr + - /tmp/scalibr:/usr/bin/scalibr command: ["/bin/sh", "-c", "/usr/bin/verify.sh || sleep infinity"] env_file: - common.env @@ -10,7 +10,7 @@ services: <<: *common image: osduplicate-rpm-rhel build: - context: . + context: .. dockerfile_inline: | FROM registry.access.redhat.com/ubi9/ubi:latest RUN dnf install -y runc @@ -24,7 +24,7 @@ services: <<: *common image: osduplicate-rpm-centos build: - context: . + context: .. dockerfile_inline: | FROM quay.io/centos/centos:stream9 RUN dnf install -y runc @@ -38,7 +38,7 @@ services: <<: *common image: osduplicate-rpm-almalinux build: - context: . + context: .. dockerfile_inline: | FROM almalinux:9 RUN dnf install -y runc @@ -52,7 +52,7 @@ services: <<: *common image: osduplicate-rpm-rockylinux build: - context: . + context: .. dockerfile_inline: | FROM rockylinux:9 RUN dnf install -y runc @@ -66,7 +66,7 @@ services: <<: *common image: osduplicate-rpm-sles build: - context: . + context: .. dockerfile_inline: | FROM registry.suse.com/bci/bci-base:15.6 RUN zypper install -y runc awk @@ -82,7 +82,7 @@ services: <<: *common image: osduplicate-rpm-opensuse build: - context: . + context: .. dockerfile_inline: | FROM opensuse/leap:15.6 RUN zypper install -y runc @@ -98,7 +98,7 @@ services: <<: *common image: osduplicate-rpm-amazonlinux2 build: - context: . + context: .. dockerfile_inline: | FROM amazonlinux:2 RUN yum install -y tomcat @@ -117,7 +117,7 @@ services: <<: *common image: osduplicate-rpm-amazonlinux2023 build: - context: . + context: .. dockerfile_inline: | FROM amazonlinux:2023 RUN dnf install -y runc diff --git a/osduplicate/rpm/verify.sh b/osduplicate/verify.sh similarity index 100% rename from osduplicate/rpm/verify.sh rename to osduplicate/verify.sh