Thank you for your interest in contributing to Brain-Storm! We welcome contributions from the community to help make blockchain education accessible to everyone.
All contributors are expected to follow our Code of Conduct. By participating, you agree to uphold this code. Please report unacceptable behaviour to the maintainers.
- Fork the repository on GitHub.
- Clone your fork locally:
git clone https://github.com/<your-username>/Brain-Storm.git - Follow the Developer Setup Guide to set up your environment.
- Create a new branch from
mainfor your change (see naming conventions below). - Make your changes, add tests, and open a pull request.
All actionable items must be tracked as GitHub Issues, not in root-level TODO files. This ensures:
- Visibility across the entire team
- Clear assignment and progress tracking
- Integration with project boards and milestones
- Historical record for future reference
Never commit TODOs or task lists to the root directory. If you encounter untracked work during development:
- Create a GitHub issue with a clear title, description, and acceptance criteria
- Link it in your PR if it's blocking or related
- Add it to the appropriate project board (e.g., "Feature Development", "Bug Fixes")
For multi-step features, break them into smaller issues or create a GitHub project to organize related issues together.
| Prefix | When to use |
|---|---|
feat/ |
New feature — e.g. feat/api-key-rotation |
fix/ |
Bug fix — e.g. fix/xss-sanitization |
docs/ |
Documentation only — e.g. docs/contract-guide |
chore/ |
Maintenance, tooling, deps — e.g. chore/upgrade-nestjs |
refactor/ |
Code restructure with no behaviour change |
test/ |
Adding or fixing tests |
Branch names must be lowercase and use hyphens, not underscores.
We enforce Conventional Commits via commitlint. Every commit message must follow this structure:
<type>(<scope>): <short summary>
[optional body]
[optional footer(s)]
| Type | Description |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation changes only |
style |
Formatting, missing semicolons — no logic change |
refactor |
Code change that is neither a fix nor a feature |
test |
Adding or correcting tests |
chore |
Build process, dependency updates, tooling |
perf |
Performance improvement |
ci |
CI/CD configuration changes |
Scopes are enforced by commitlint. Use the most specific scope that applies. Valid values are:
Domain scopes (business logic area):
| Scope | When to use |
|---|---|
auth |
Authentication, authorization, JWT, sessions |
courses |
Course management, enrollment, progress |
users |
User profiles, settings, roles |
stellar |
Stellar/Soroban blockchain integration |
notifications |
Email, push, in-app notification system |
payments |
Stripe integration, payments, subscriptions |
reputation |
Reputation scores, badges, leaderboards |
search |
Search indexing, Elasticsearch integration |
analytics |
Analytics tracking, reporting, dashboards |
Workspace scopes (monorepo package):
| Scope | When to use |
|---|---|
backend |
apps/backend — NestJS REST API |
frontend |
apps/frontend — Next.js web app |
sdk |
packages/sdk — TypeScript client SDK |
types |
packages/types — shared TypeScript types |
mobile |
packages/mobile / packages/mobile-app |
api |
packages/api — API load-testing package |
Contract scopes (Soroban smart contracts):
Note: commitlint splits scope on
/, so contract scopes use hyphens — e.g.contracts-tokenmaps tocontracts/token/.
| Scope | Contract directory (contracts/…) |
|---|---|
contracts |
Cross-contract or workspace-level |
contracts-analytics |
analytics/ |
contracts-badges |
badges/ |
contracts-buyback |
buyback/ |
contracts-certificate |
certificate/ |
contracts-credential-metadata |
credential_metadata/ |
contracts-dispute |
dispute/ |
contracts-escrow |
escrow/ |
contracts-governance |
governance/ |
contracts-grants |
grants/ |
contracts-integration |
integration/ |
contracts-liquidity-pool |
liquidity_pool/ |
contracts-market |
market/ |
contracts-nft |
nft/ |
contracts-registry |
registry/ |
contracts-reputation |
reputation/ |
contracts-royalty-distribution |
royalty_distribution/ |
contracts-scholarship-fund |
scholarship_fund/ |
contracts-shared |
shared/ |
contracts-token |
token/ |
contracts-token-restrictions |
token_restrictions/ |
Tooling scopes:
| Scope | When to use |
|---|---|
ci |
GitHub Actions workflows, CI configuration |
docker |
Dockerfile, docker-compose files |
infra |
Terraform, Helm, Kubernetes manifests |
deps |
Dependency upgrades (npm or Cargo) |
security |
Security scanning config, deny.toml, audit fixes |
docs |
Documentation only (no code changes) |
release |
Release configuration, changelogs, versioning |
Omitting the scope is allowed for truly cross-cutting changes (e.g. chore: bump Node.js in CI).
feat(auth): add API key authentication for service-to-service calls
fix(courses): strip HTML from description before saving
docs(contracts-certificate): add end-to-end credential issuance flow
chore(deps): upgrade @stellar/stellar-sdk to v13
fix(security): patch adm-zip path traversal vulnerability
feat(contracts-reputation): add weighted scoring algorithm
Breaking changes must include BREAKING CHANGE: in the footer:
feat(auth)!: remove legacy /v0 endpoints
BREAKING CHANGE: All clients must migrate to /v1 endpoints.
# Validate a commit message (exit 0 = valid)
echo "feat(auth): add OAuth2 support" | npx commitlint
# The husky commit-msg hook runs this automatically on every commit- Keep PRs small and focused — one logical change per PR.
- Fill in the PR template completely.
- Ensure all CI checks pass before requesting review.
- Link the related issue using
Closes #<issue-number>in the PR description. - Request at least one review from a maintainer.
- Address all review comments before merging.
- PRs are merged via squash merge to keep a clean history.
Before submitting your PR, verify:
- Code follows the style guide (
npm run lintpasses). - All existing tests pass (
npm run test). - New tests are added for new behaviour.
- Commits follow Conventional Commits format with an allowed scope.
- Documentation is updated if behaviour changes.
- No secrets or PII are committed.
- No commented-out code is left behind; git history keeps the old version.
- PR description clearly explains the what and why.
# Install dependencies
npm install
# Run backend in watch mode
npm run dev:backend
# Run tests
cd apps/backend && npm test
# Lint
npm run lintSee docs/contributing/setup.md for the full setup guide.
The .husky/pre-commit hook runs automatically before every commit. It only checks file types that are staged:
| Staged files | Checks run |
|---|---|
apps/frontend/** |
eslint, prettier --check |
apps/backend/** |
eslint, prettier --check |
| Root config files | prettier --check |
contracts/**/*.rs |
cargo fmt --all -- --check, cargo clippy |
If you stage only JS/TS files, the Rust checks are skipped — pure-JS commits are unaffected in speed. To fix a formatting failure from the Rust check run:
cargo fmt --allTo fix a Clippy failure, address the warning reported on stderr before re-committing.
We maintain a shared test-fixtures module in packages/types/src/test-utils/ that exports factory functions for every shared domain type. Always use these factories instead of hand-building inline mock objects in tests.
Hand-built mocks like { id: '1', email: 'test@example.com' } as User drift from the real type definition whenever a field is added, renamed, or removed. Factories catch those changes at the point of definition instead of silently diverging across dozens of test files.
import {
UserFactory,
CourseFactory,
EnrollmentFactory,
QuizFactory,
CredentialFactory,
ProgressFactory,
PaymentFactory,
} from '@brain-storm/types/test-utils';// Single object with defaults
const user = UserFactory.create();
// Single object with overrides
const admin = UserFactory.create({ role: 'admin', email: 'admin@example.com' });
// Batch of objects
const students = UserFactory.createMany(10, { role: 'student' });
// Cross-entity relationship
const userId = 'user-123';
const courseId = 'course-456';
const enrollment = EnrollmentFactory.create({ userId, courseId, status: 'active' });
const progress = ProgressFactory.create({ userId, courseId, progressPct: 75 });
const credential = CredentialFactory.create({ userId, courseId, status: 'issued' });
const payment = PaymentFactory.create({ userId, courseId, amount: 4999 });- Pin fields you assert on — don't rely on random defaults for test assertions; always override the fields your test cares about.
- Share IDs explicitly — when two entities must relate, pass the same
userId/courseIdto both factories rather than letting them each generate independent IDs. - Don't mutate factory output — each call produces an independent plain object.
- Add new factories whenever you add a new shared type:
- Define the interface in
packages/types/src/test-utils/index.ts - Add
create()andcreateMany()factory methods - Export from the index
- Add tests in
packages/types/src/test-utils/factories.test.ts
- Define the interface in
For a full reference see packages/types/src/test-utils/README.md.
If you discover a security vulnerability, do not open a public issue. Follow our Security Policy for responsible disclosure.
We run npm audit in CI on every push. The policy is:
- Critical and high severity vulnerabilities that have a non-breaking fix available must be patched before merging.
- Vulnerabilities whose only fix requires a semver-major upgrade (breaking change) are tracked as accepted-risk exceptions below, with justification and a planned upgrade milestone.
- Moderate and low severity findings are reviewed quarterly and patched opportunistically.
The following high/critical vulnerabilities cannot be resolved without semver-major breaking upgrades as of the last audit (2026-09-26). Each is accepted for the stated reason and must be revisited on the target date.
| Package | Severity | Vulnerability | Fix requires | Justification | Target |
|---|---|---|---|---|---|
next |
critical | DoS via Image Optimizer / HTTP request deserialization | next@16 |
Next.js 14→16 is a major migration requiring App Router rewrites. Tracked in separate upgrade ticket. Mitigated by running behind a load balancer that rejects oversized requests. | Q4 2026 |
vitest / @vitest/coverage-v8 |
critical | Arbitrary file read/execute via Vitest UI server | vitest@5 |
Dev-only dependency. Vitest UI server is never exposed outside developer workstations. Mitigated by never starting --ui in CI or production. |
Q4 2026 |
@nestjs/platform-express / multer |
high | Multer DoS via resource exhaustion | @nestjs/platform-express@12 |
NestJS 10→12 requires testing all decorators and interceptors. Mitigated by global file-size limits (multipart/form-data max 10 MB enforced via Nginx). |
Q4 2026 |
@nestjs/graphql / ws |
high | WebSocket DoS / header count | @nestjs/graphql@14 |
NestJS GraphQL major upgrade; requires schema compatibility review. Mitigated by helmet rate-limiting on WebSocket upgrades. |
Q4 2026 |
@nestjs/swagger via js-yaml / lodash |
high | Prototype pollution, code injection in _.template |
@nestjs/swagger@12 |
Swagger is read-only documentation UI, never used in data-path. lodash.template is not called with user-controlled input in this codebase. |
Q1 2027 |
@opentelemetry/sdk-node (incl. exporter-prometheus, propagator-jaeger) |
high | Prometheus exporter DoS / Jaeger header DoS | @opentelemetry/sdk-node@0.222 |
Observability stack upgrade; requires coordinator work with infra team. Prometheus endpoint is not publicly reachable (internal only). Jaeger header parsing only triggered on trusted internal traffic. | Q1 2027 |
@sentry/nextjs via rollup |
high | Rollup path traversal during build | @sentry/nextjs@11 |
Build-time only. Rollup path traversal cannot be triggered at runtime. Production builds run in isolated CI containers. | Q1 2027 |
@stellar/stellar-sdk via toml |
high | toml-node uncontrolled recursion / prototype pollution | @stellar/stellar-sdk@17 |
SDK v17 changes the Horizon + RPC client API surface significantly. Mitigated by validating all TOML inputs against a known-good schema before parsing. | Q1 2027 |
eslint-config-next via glob |
high | glob CLI command injection | eslint-config-next@16 |
Dev-only; glob CLI is never invoked in production. Command injection requires an attacker to control glob arguments, which is not possible in our ESLint configuration. |
Q4 2026 |
@storybook/nextjs via sharp |
high | sharp/libvips CVEs | @storybook/nextjs@10 |
Dev-only; Storybook never runs in production. Image processing done via sharp is only for component preview screenshots in developer browsers. |
Q1 2027 |
@faker-js/faker |
high | helpers.fake arbitrary code execution |
@faker-js/faker@10 |
Dev/test only. helpers.fake() is not called with user-controlled template strings in our test suite. |
Q4 2026 |
postcss |
high | XSS via unescaped </style> / sourceMappingURL file read |
next@16 |
Build-time only; PostCSS output is sanitized by Next.js before serving. sourceMappingURL injection requires write access to input CSS, which is only developer-controlled. |
Q4 2026 |
To re-run the audit and update this table:
npm audit --json | python3 -c "
import json, sys
d = json.load(sys.stdin)
v = d['vulnerabilities']
hc = {k: x for k, x in v.items() if x.get('severity') in ('high', 'critical')}
for k, x in hc.items():
print(k, x['severity'], x.get('fixAvailable'))
"All Rust crates in the Cargo workspace are checked against our license allow-list by cargo-deny in CI.
| License | Notes |
|---|---|
| MIT | Standard permissive |
| Apache-2.0 | Standard permissive |
| Apache-2.0 OR MIT | Dual-licensed (common in Rust ecosystem) |
| BSD-2-Clause | Simplified BSD |
| BSD-3-Clause | Modified BSD |
| ISC | Functional equivalent to MIT |
| Unicode-DFS-2016 | Required by unicode-ident (Unicode data tables) |
| License | Reason |
|---|---|
| GPL-2.0 | Copyleft; incompatible with commercial use |
| GPL-3.0 | Copyleft; incompatible with commercial use |
| AGPL-3.0 | Strong copyleft; incompatible with SaaS deployment |
Any crate with an unlicensed or undetectable license is denied (unlicensed = "deny").
Before adding a Rust crate:
- Verify its license is in the allow-list above.
- Run
cargo deny check licenseslocally (requirescargo install cargo-deny). - If the crate uses a license not in the list, open an issue to discuss adding it before merging.
# Install (once)
cargo install cargo-deny
# Check all rules (advisories, licenses, bans, sources)
cargo deny check
# Check licenses only
cargo deny check licenses
# Check for known security advisories
cargo deny check advisoriesThe deny configuration lives in deny.toml at the workspace root. Key settings:
- advisories: vulnerability and unmaintained crate warnings sourced from the RustSec Advisory Database.
- licenses: allow-list defined above; any crate not matching is denied in CI.
- bans: multiple versions of the same crate produce a warning (not an error) to keep the dependency tree clean.
- sources: only
crates.ioregistry is allowed; no unknown git sources.
All REST endpoints are prefixed with /v1. Before introducing any breaking change you must follow the process in docs/api-versioning.md. In short:
- Implement the change under a new prefix (
/v2/...) — never modify/v1in place. - Mark the old endpoint deprecated in Swagger (
@ApiOperation({ deprecated: true })). - Add
DeprecationandSunsetresponse headers to the old endpoint. - Use a
feat!:orBREAKING CHANGE:commit so Release Please bumps the major version. - Keep both versions running for at least 90 days before removing
/v1.
See docs/api-versioning.md for the full strategy, deprecation timeline, and migration examples.
The following scripts were used once to migrate the codebase and have since been removed (issue #1197). They are documented here for historical reference only — do not recreate them.
| Script | What it did | Applied in |
|---|---|---|
fix-catch-clauses.mjs |
Added unknown typing to catch (err) / catch (error) clauses in the backend |
Committed prior to removal |
fix-test-casts.mjs |
Added as TestUserInput casts to service.create() calls in integration specs |
Committed prior to removal |
fix-ts2564.mjs |
Added ! definite-assignment assertions to a subset of entity properties (TS2564) |
Committed prior to removal |
fix-ts2564-all.mjs |
Broader pass of the same TS2564 fix across all DTO and entity files | Committed prior to removal |
All fixes from these scripts are already committed to the codebase. The scripts themselves were removed once verified. If you need to perform a similar mass codebase transformation in the future, create a new migration script under scripts/ (not the repo root), run it, commit the result, and remove the script in the same PR.
Checked against backend source. @elastic/elasticsearch is used by @nestjs/elasticsearch, and @nestjs/platform-socket.io provides the socket.io adapter for the notifications gateway. Both are used indirectly, so neither is removed.