This document is for anyone wanting to contribute to the implementation of the security tracker.
This document is for anyone wanting to contribute to the implementation of the security tracker. It contains general contribution information, and lists resources to help you get started:
- Architecture Overview: High-level system design and component interaction.
- Architecture Diagram: Visual representation of the system (Mermaid source).
- Design Documents: Detailed design specifications for individual features (E.g., linkage).
Other directories in this repository have additional README.md files with more specific information relevant to their sibling files.
Service definitions are in nix/configuration.nix.
Application logic lives in the src/ directory.
From here, it follows standard Django patterns:
src/project/: global project configurationsrc/shared/: application with data models and business logicsrc/webview/: application for the web frontend
The service is implemented in Python using Django. It is built and deployed with Nix.
To get going, all you need is to install Nix.
Start a development shell:
nix-shellThis will provide most of tools necessary to run the service locally.
Note
If you want to start the development environment automatically when entering the project directory, set up nix-direnv on your system.
Add your .envrc to .git/info/exclude.
List all available management commands:
manage helpA formatter is run on each pull request and as a pre-push Git hook.
Run the formatter manually with:
nix-shell --run formatA pull request asks maintainers to accept responsibility for a decision. Help them understand what they're agreeing to.
To minimise turnaround time for getting your contribution merged:
-
Make exactly one change in each pull request.
Don't lump together unrelated changes. Otherwise, easy parts that could be merged on their own get blocked by the harder ones that need multiple iterations to get right.
-
Always add tests when changing behavior or fixing bugs.
Ideally, start by adding tests.
Even contributions that consist entirely of new tests annotated with
@pytest.mark.xfail(reason="Not implemented")are welcome! This is a good way of formalising requirements to be implemented in the future. -
Use the commit message title to describe the change such that its merit can be evaluated.
- Good:
fix: race condition during ingestion - Bad:
fix: add with transaction.atomic() in ingestion.py
- Good:
-
If the change is not trivial, explain why the change is made in the pull request description and commit message.
Also describe consequences of the change if they aren't obvious.
Empty pull request descriptions and commit messages are fine if rationale and impact are evident from the title.
-
Strive to keep the diff small.
Larger changes typically mean that you made too many changes at once. Exceptions are mechanical changes that can be checked at a glance or reproduced by running a command.
-
Don't rewrite history, address review comments in new commits.
The pull request should still amount to a small change, and commits can be squashed before merging.
-
Run
nix-shell --run formatandnix-build -A testsbefore pushing.
If you want to accept fixups by maintainers, make your fork writeable. This allows for last-minute changes or resolving merge conflicts without your involvement.
We use these tagged comments inspired by and loosely following PEP 450:
-
TODO- Unfinished change, should not occur in productionWe haven't adopted this pattern from the start, so there are still many
TODOs that should beFIXMEs. Please only replace instances when touching the respective code.# FIXME(@fricklerhandwerk): Remove the above note when the last instance of `TODO` is gone. -
FIXME- Known bad practice or hack, but too expensive or of questionable value to fix at the momentWe use this to communicate to readers of the code where careful improvements are welcome, but weren't considered critical at the time of writing and thus won't be tracked as an issue. We only use issues to track desired changes to behavior observable by users.
-
XXX- Explanation for why unusual code is the way it isWe use this to ask readers for extra attention to code that may be surprising but shouldn't be changed without particular care.
We haven't adopted this pattern from the start, so there are still some
NOTEs that should beXXXs. Please only replace instances when touching the respective code.# FIXME(@fricklerhandwerk): Remove the above note when the last instance of `NOTE` is gone.
Always add your GitHub handle in parentheses -- (@<author>) -- so it's clear who had an opinion and may still have one during review.
Code may move around, so git blame won't be useful to track comment authorship.
You will need a local instance of the database to run tests and experiment manually.
Currently only PostgreSQL is supported as a database.
Assuming you have a local checkout of this repository at ~/src/nix-security-tracker, in your NixOS configuration, add the following entry to imports and rebuild your system:
{ ... }:
{
imports = [
(import ~/src/nix-security-tracker { }).dev-setup
];
nix-security-tracker-dev-environment = {
enable = true;
# The user you run the backend application as, so that you can access the local database
user = "myuser";
};
}To replicate this on a traditional Unix-like system:
- Inspect the local database configuration
- Read the documentation on the respective module options for the general idea, e.g.
services.postgresql.ensureDatabases - Search the linked module source for the option names for implementation details, e.g.
postgresql.nix
![NOTE] For a quick start, create dummy credentials:
dummy-credentialsLogging in and publishing issues requires setting up credentials.
Run the server:
manage runserverFetch the tips of all channel branches:
manage fetch_all_channelsSelect a ``head_sha1_commit` from the output and run evaluation on that:
manage run_evaluation <commit>Matching CVEs against Nixpkgs metadata is triggered by pgpubsub notifications internally as CVEs are ingested.
To test this dataflow locally, start the listeners:
manage listen -v3 --recoverIngest some CVEs:
manage ingest_bulk_cve --from 2024-01-01 --to 2024-01-31This should produce untriaged matches.
In order to start over you need SSH access to the staging environment. Tools for the following are available in the development shell. Delete the database and recreate it, then restore it from a dump, and (just in case the dump is behind the code) run migrations:
dropdb nix-security-tracker
ssh root@tracker-staging.security.nixos.org "sudo -u postgres pg_dump --create nix-security-tracker | zstd" | zstdcat | pv | psql
manage migrateThe service connects to GitHub for certain operations:
- Managing permissions according to GitHub team membership in the configured organisation
- Publishing vulnerabilities as GitHub issues
This requires setting up GitHub credentials.
Create a Django secret key
python3 -c 'import secrets; print(secrets.token_hex(100))' > .credentials/SECRET_KEYSet up GitHub authentication
-
Create a new or select an existing GitHub organisation to associate with the Nixpkgs security tracker.
We're using https://github.com/Nix-Security-WG for development.
- In the Settings tab under Personal access tokens, ensure that personal access tokens are allowed.
- In the Teams tab, ensure there are at two teams for mapping user permissions.
They will correspond to
nixpkgs-committersandsecurity. - In the Repositories tab, ensure there's a repository for posting issues.
It will correspond to
nixpkgs. In the Settings tab on that repository, in the Features section, ensure that Issues are enabled.
-
In the GitHub organisation settings configure the GitHub App
We're using https://github.com/apps/sectracker-testing for local development and https://github.com/apps/sectracker-demo for the public demo deployment. Register a new GitHub application if needed.
-
In Personal access tokens approve the request under Pending requests if approval is required
-
In GitHub Apps, go to Configure and then App settings (top row). Under Permissions & events (side panel):
- In Repository Permissions select Administration (read-only), Issues (read and write), and (Metadata: read-only).
- In Organization Permissions select Administration (read-only) and (Members: read-only).
Store the Client ID in
.credentials/GH_CLIENT_ID -
In the application settings / General / Generate a new client secret
Store the value in
.credentials/GH_SECRET -
In the application settings / General / Private keys / Generate a private key
Store the value in
.credentials/GH_APP_PRIVATE_KEY -
In the application settings / Install App
Make sure the app is installed in the correct organisation's account.
If the account that shows up is your Developer Account
In the application settings / Advanced
- Transfer ownership of this GitHub App to the organisation account.
-
In organisation settings under GitHub Apps / Installed GitHub Apps / <GH_APP_NAME> / Configure page
Check the URL, which has the pattern
https://github.com/organizations/<ORG_NAME>/settings/installations/<INSTALLATION_ID>.Store the value <INSTALLATION_ID> in
.credentials/GH_APP_INSTALLATION_ID.
-
Set up Github App webhooks
For now, we require a GitHub webhook to receive push notifications when team memberships change. To configure the GitHub app and the webhook in the GitHub organisation settings:
- In Code, planning, and automation Webhooks, create a new webhook:
- In Payload URL, input "https://<APP_DOMAIN>/github-webhook".
- In Content Type choose application/json.
- Generate a token and put in Secret. This token should be in
./credentials/GH_WEBHOOK_SECRET. - Choose Let me select individual events
- Deselect Pushes.
- Select Memberships.
On NixOS, you can run the service in a systemd-nspawn container to preview a deployment.
Assuming you have a local checkout of this repository at ~/src/nix-security-tracker, in your NixOS configuration, add the following entry to imports and rebuild your system:
{ ... }:
{
imports = [
(import ~/src/nix-security-tracker { }).dev-container
# ...
];
}The service will be accessible at http://172.31.100.1.
Run integration tests:
nix-build -A testsInteract with the involved virtual machines in a test:
$(nix-build -A tests.driverInteractive)/bin/nixos-test-driver
Whenever you add a field in the database schema, run:
manage makemigrationsThen before starting the server again, run:
manage migrate
This is the default Django workflow.
The application uses django-pgpubsub to react to database changes asynchronously.
Listeners are defined as functions decorated with @pgpubsub.post_insert_listener, @pgpubsub.post_update_listener etc., and are primarily located in the src/shared/listeners/ directory.
To ensure your listener is proactively registered when the Django application starts, its containing module must be imported. We use the following pattern:
-
Create or edit a listener module in
src/shared/listeners/(E.g.,src/shared/listeners/my_new_listener.py). -
Import the module inside
src/shared/listeners/__init__.pyso it's loaded as part of the package:# inside src/shared/listeners/__init__.py import shared.listeners.my_new_listener # noqa
-
src/shared/apps.pytriggers these imports in itsready()method by importingshared.listeners, registering all listeners upon app initialization.
Warning
If you create a new listener module but forget to add its import to src/shared/listeners/__init__.py, your listener will fail to run silently!
Suggestion contents are displayed from a cache to avoid latency from complex database queries.
To compute or re-compute the cached information from scratch:
manage regenerate_cached_suggestionsSee infra/README.md.
Sentry-like collectors are endpoints where we ship error information from the Python application with its stack-local variables for all the traceback, you can use Sentry or GlitchTip as a collector.
Collectors are configured using a DSN, i.e. a data source name. in Sentry parlance, this is where events are sent to.
You can set GLITCHTIP_DSN as a credential secret with a DSN and this will connect to a Sentry-like endpoint via your DSN.
This project uses plain CSS with a utility-class approach. Utility classes make it possible to reuse sec-traker's existing UI elements without needing contributors to write any css.
Rather than styling semantic classes, utility classes refer to UI elements directly.
E.g. rounded-box for a standard container with rounded corners that we reuse across the project.
Flex containers are use extensively as they are versatile and responsive.
E.g row + gap + center to organize elements on a row, separated by gaps of the same standard size, and centered vertically.
This design gives us a simple UI language that is easy to deploy and consistent (consistent colors, space sizes, etc).
The CSS is organized into multiple CSS files, in src/webview/static, that are loaded in src/shared/templates/base.html. Consult each one for role and documentation. utility.css should contain all the classes you need for html templates.
Icons rely on a custom icomoon webfont and class definitions to be used with the <i> tag. Consult [src/webview/static/icons/README.md] for details.
Adding new styles should be a last resort:
- Check existing utilities first in utility.css - Reusing what exists is what guarantees UI consistency and mainainability
- Add to utility.css - If it's a real new and reusable pattern, add it as a utility class
- Use consistent naming - Follow the existing naming conventions
- Document new utilities - Update this guide if adding significant new patterns