From 045dc42d0a2cc5504bc4cabf8808358352da0ada Mon Sep 17 00:00:00 2001 From: DaC Date: Thu, 8 Oct 2026 21:22:52 +0200 Subject: [PATCH 1/4] test: add safe CRM negative-control fixture --- crm_safe_simulato.html | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 crm_safe_simulato.html diff --git a/crm_safe_simulato.html b/crm_safe_simulato.html new file mode 100644 index 0000000..7884c47 --- /dev/null +++ b/crm_safe_simulato.html @@ -0,0 +1,29 @@ + + + + + + Chimera — Safe CRM Control Fixture + + + +

CRM control — safe rendering

+

Negative control for Chimera's deliberately vulnerable CRM fixture. This page keeps pasted input inert as text.

+ + + +
I dati salvati appariranno qui come testo.
+ + + From 2ce94601728395509b0969de0a7ce8b3b6c22e81 Mon Sep 17 00:00:00 2001 From: DaC Date: Thu, 8 Oct 2026 21:23:01 +0200 Subject: [PATCH 2/4] test: assert both clipboard-to-CRM fixture contracts offline --- tests/test_fixture_contracts.py | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/test_fixture_contracts.py diff --git a/tests/test_fixture_contracts.py b/tests/test_fixture_contracts.py new file mode 100644 index 0000000..562111c --- /dev/null +++ b/tests/test_fixture_contracts.py @@ -0,0 +1,50 @@ +"""Offline structural fixture checks; browser execution is not tested here.""" +from html.parser import HTMLParser +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[1] + + +class IdCollector(HTMLParser): + def __init__(self): + super().__init__() + self.ids = set() + + def handle_starttag(self, tag, attrs): + props = dict(attrs) + if "id" in props: + self.ids.add(props["id"]) + + +class FixtureContractTests(unittest.TestCase): + def test_source_fixture_contains_copy_event_and_local_receiver(self): + text = (ROOT / "vittima.html").read_text(encoding="utf-8") + self.assertIn("addEventListener('copy'", text) + self.assertIn("clipboardData.setData('text/plain'", text) + self.assertIn("http://127.0.0.1:5000", text) + + def test_positive_and_negative_controls_have_same_input_output_ids(self): + for name in ("crm_scammer_simulato.html", "crm_safe_simulato.html"): + parser = IdCollector() + parser.feed((ROOT / name).read_text(encoding="utf-8")) + self.assertTrue({"nameInput", "sinkArea"} <= parser.ids, name) + + def test_positive_control_intentionally_uses_unsafe_sink(self): + text = (ROOT / "crm_scammer_simulato.html").read_text(encoding="utf-8") + self.assertIn(".innerHTML =", text) + + def test_negative_control_keeps_input_as_text(self): + text = (ROOT / "crm_safe_simulato.html").read_text(encoding="utf-8") + self.assertIn(".textContent =", text) + self.assertNotIn(".innerHTML", text) + self.assertNotIn("document.cookie", text) + + def test_readme_restricts_use_to_authorized_labs(self): + text = (ROOT / "README.md").read_text(encoding="utf-8") + self.assertIn("explicit ownership and authorization", text) + self.assertIn("not a browser exploit", text) + + +if __name__ == "__main__": + unittest.main() From a65e99d881688542bc270c7e7f7279301a10da22 Mon Sep 17 00:00:00 2001 From: DaC Date: Thu, 8 Oct 2026 21:23:03 +0200 Subject: [PATCH 3/4] ci: validate Chimera fixtures without executing receiver --- .github/workflows/fixture-integrity.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/fixture-integrity.yml diff --git a/.github/workflows/fixture-integrity.yml b/.github/workflows/fixture-integrity.yml new file mode 100644 index 0000000..1b8654b --- /dev/null +++ b/.github/workflows/fixture-integrity.yml @@ -0,0 +1,25 @@ +name: Chimera fixture integrity + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + offline-fixtures: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Compile Python files without executing receiver + run: python -m py_compile c2_server.py make_svg.py + - name: Parse standalone JavaScript + run: node --check script.js + - name: Check positive and safe-negative fixtures + run: python -m unittest discover -s tests -v From fc03108fb5902bfe0801fcc17ae2f5c5699f7f83 Mon Sep 17 00:00:00 2001 From: DaC Date: Thu, 8 Oct 2026 21:23:20 +0200 Subject: [PATCH 4/4] docs: explain positive and safe negative control experiments --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 4b27e9b..ac4c29c 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,21 @@ A historical project report written during the initial experiment. It contains s Treat this README and the executable code as the current description of the project. +## Negative control: safe CRM rendering + +The repository now includes two destination fixtures with the same input/output IDs: + +| Fixture | Rendering | Expected observation | +| --- | --- | --- | +| `crm_scammer_simulato.html` | Deliberately unsafe `innerHTML` sink | The copied HTML-like suffix can become markup under compatible browser conditions. | +| `crm_safe_simulato.html` | `textContent` sink | The same copied value remains inert, visible text. | + +This paired design makes it possible to compare a positive case with a concrete safe negative control, instead of treating execution in a deliberately vulnerable fixture as sufficient evidence of a broader exploit. + +The safe fixture does not collect cookies or transmit data. The included CI checks file structure, source syntax, and intended sinks **without running any browser payload or receiver**. Those static checks are not a substitute for a controlled browser compatibility matrix. + +To use the fixtures manually, keep the experiment on local loopback and compare the output of both CRM pages with the same controlled sample. Do not expose `c2_server.py` to a shared network: its historical implementation binds to all interfaces and runs in debug mode. + ## What actually triggers execution The clipboard is only a carrier.