-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
There is no CONTRIBUTING.md in the repository. The
README holds the
quick start and the short form of the conventions; this page is the longer
version, meant for the moment you actually have the tree open and a check has
just gone red.
Most changes touch frontend/ alone. That is the application: the STIX logic,
the canvas, the storage, all of it runs in the browser. If your change stays
there, Node is the only tool you need.
| You are touching | You need | Why exactly |
|---|---|---|
frontend/ |
Node 22 | CI pins node-version: "22" and nothing else is tested |
backend/ |
uv, Python 3.12 or later, plus libmagic1 on the machine |
pyproject.toml sets requires-python = ">=3.12"; CI installs libmagic1 before syncing, with the comment "required by pycti" |
enricher/ |
uv, Python 3.13 or later | requires-python = ">=3.13" |
backend/ is not a service and never runs in production. It is the reference
STIX implementation that generates the golden vectors, and the README explains
why that trade was made.
On Debian or Ubuntu the pycti prerequisite is sudo apt-get install libmagic1.
Without it, uv run pytest in backend/ fails at import time, before a single
test runs, which reads as a broken checkout rather than a missing system
library.
Both lockfiles are committed, so uv sync gives you the exact ruff, pycti and
stix2 that CI uses (ruff 0.16.0, pycti 7.260722.0, stix2 3.0.2 at the time of
writing).
frontend/src/stix/generated/validators.mjs is not committed. It is
compiled from the vendored OASIS schemas by scripts/build-validators.mjs,
which runs from the predev, prebuild and pretest hooks in
package.json. The reason it is precompiled rather than built at runtime: the
production CSP forbids unsafe-eval, and ajv compiles schemas through
new Function.
The practical consequence is that npx vitest does not work on a fresh clone.
It skips the pretest hook, and the validation tests fail on a module that was
never written. Run npm test, which is the same suite with the hook.
golden-vectors.json and golden-bundle.json are committed but generated.
Never edit them by hand. See the golden vectors below.
Three test suites and two lint passes. There is no linter for the frontend, no
ESLint anywhere in the tree: the type-check is what stands in its place, with
strict, noUnusedLocals, noUnusedParameters and
noFallthroughCasesInSwitch on in tsconfig.json.
| Directory | Command | What it actually proves |
|---|---|---|
frontend |
npm test |
29 test files under vitest: the golden identifiers replayed case by case, the golden bundles rebuilt object for object, export/import roundtrips, the store against fake-indexeddb, OASIS validation, the investigation lint, and the English-interface guard |
frontend |
npx tsc -b |
the type-check, exactly as CI runs it |
frontend |
npm run build |
tsc -b, then vite over three entry points (index, guide, about), then prerender.mjs, which turns dist/guide.html and dist/about.html into routed pages with their content already in the HTML |
backend |
uv run ruff check . |
rules E, F, I, UP, B, SIM at 100 columns |
backend |
uv run pytest -q |
every identifier recipe against pycti, roundtrip on the golden fixture, the relationship matrix, export and import |
enricher |
uv run ruff check . |
ruff's default rule set at 100 columns, since the config sets nothing but the line length |
enricher |
uv run pytest -q |
the API surface, the hardening, and one file per enricher |
npm run build type-checks again, so the separate tsc -b step covers no extra
ground. It buys an unambiguous failure early instead of a build that dies at the
end for a reason you have to read backwards.
The enricher suite patches the tool runner and the HTTP client everywhere, so it
needs neither the network nor dig, whois or subfinder on your machine. If
a test of yours reaches out for real, it will pass on your laptop and hang in
CI.
The enricher pins only line-length. That is worth knowing before you refresh
uv.lock: ruff's defaults are not frozen, and a version bump has already turned
the job red on import ordering without a line of code changing.
.github/workflows/ci.yml runs on every pull request and on pushes to main.
Three independent jobs, so a red one tells you where the damage is.
| Job | Steps | A failure means |
|---|---|---|
frontend |
npm ci, npx tsc -b, npm test, npm run build
|
the application itself: types, behaviour, or the build |
stix-core |
ruff, pytest, then regenerate both golden files and demand an empty git diff
|
either the Python oracle broke, or the committed vectors no longer match what the oracle produces |
enricher |
ruff, pytest | the sidecar, which nothing else depends on |
Read the annotations, not the logs. GitHub exposes annotations publicly but
gates the logs behind repository rights, so a contributor on a fork sees a red
cross and nothing else. The workflow works around this deliberately: both lint
steps echo the ruff version and its rule statistics as a ::notice::, and the
golden-vector check carries the first lines of the real diff into its
::error::. A check that says "out of sync" without saying how forces you to
re-run CI blind, and that is the failure mode being prevented.
Two implementations of the identifiers exist and have to agree: the Python one
in backend/app/stix_core/ids.py, which is the oracle and is itself pinned to
pycti by its own tests, and the TypeScript one in frontend/src/stix/ids.ts,
which is the one that runs in your browser. The recipes themselves, property by
property, are specified in
docs/identifiers.md;
this section is only about the workflow.
Two scripts, and you rarely need both:
| You changed | Run, from backend/
|
It rewrites |
|---|---|---|
an identifier recipe (app/stix_core/ids.py) |
uv run python scripts/generate_golden_vectors.py |
frontend/src/stix/golden-vectors.json (41 object cases, 12 observable cases) |
| the bundle builder or the importer | uv run python scripts/generate_golden_bundle.py |
frontend/src/stix/golden-bundle.json (one fixture investigation, exported three ways, with each fingerprint and each warning) |
Then run npm test in frontend/, because those two files are what
stix/ids.test.ts and stix/bundle.test.ts replay. Commit the regenerated JSON
in the same pull request as the change that moved it, or the stix-core job
fails on the diff.
Yes, it is awkward: a Python script in backend/ owns two source files under
frontend/src/. It is that way round because the Python side is the oracle and
the TypeScript side is what has to catch up.
Two things worth being careful about.
A failing pycti comparison is not a vector to regenerate. test_stix_ids.py
says it in its own header: if one of those assertions breaks, OpenCTI changed
its algorithm, and adjusting the expected value without realigning
ids.py on the new pycti is how bundles start creating duplicates on import.
Regenerating makes the two sides agree, not correct. If a vector moves, an
identifier moved with it, and an identifier that moves turns somebody's next
re-import into a duplicate rather than an update. Say in the pull request which
recipe changed and why. golden-bundle.json moving on its own is milder: it
usually means the builder now emits a different field, and the fingerprint
followed.
Branch names, Conventional Commits and one issue per pull request are in the README's Conventions section, and are not restated here.
Nothing enforces any of it, no hook and no CI job. git log is the reference if
you are unsure what a message is supposed to look like.
One issue per pull request is worth more than tidiness: commit bodies here say what was wrong and what was rejected as a fix, and a pull request carrying two unrelated changes cannot do that for either of them.
The rule is the README's Language bullet: English everywhere a reader meets it, no i18n layer, three deliberate exceptions. What that bullet does not say is where the tree has already drifted from it, and what enforces the rest.
-
test descriptions, the
describeanditlabels, which only ever surface in the output of a test run; -
commit messages, per the README. The history since 1.0 is in English
though, so read
git lograther than this sentence if you want to match what is actually there; -
golden-vector fixtures keep their non-ASCII names (
Opération Héron,Δοκιμή Unicode,ПлохойСофт). They exist to exercise JCS canonicalisation on non-ASCII, and translating them would delete the thing under test.
The rule is executable, up to a point. frontend/src/i18n.test.ts strips
comments from every .tsx file that is not a test, then looks for accented
Latin strings in JSX text and string literals. A French comment has nothing to
fear, and that is the intent.
Its two blind spots are worth knowing, because both have already let a string
through. It reads .tsx only, so French sitting in a .ts module is invisible
to it. And it keys on accents, so an unaccented French label passes untouched:
that is exactly how one survived in the export result until a human noticed.
Privately, and not as a public issue: the process is in SECURITY.md, which also draws the line between a security report and a normal bug.
- How-it-is-built for why the pieces are arranged this way.
-
Enrichment-sidecar if your change is in
enricher/. - Troubleshooting when something misbehaves at runtime rather than in a test.
- Self-hosting for running a build you just made.
Using it
Running it
Changing it
When it breaks
In the application:
In the repository: