An offline assurance layer that verifies the data, the model and the outputs of a computer-vision pipeline, then proves every decision.
For the proposed single-project cloud deployment, see Vercel services setup and hosting constraints.
- Overview
- Key Features
- How It Works
- Architecture
- Tech Stack
- Project Structure
- Getting Started
- Data Setup and Ingestion
- Configuration
- Usage
- Security and Access Control
- Testing and Benchmarks
- Troubleshooting
- Project Status and Limitations
- Roadmap
- Contributing
- License
Computer-vision systems depend on datasets, pretrained models and third-party components whose integrity is usually assumed rather than verified. A poisoned dataset, a hidden backdoor or a silently swapped model can make a system fail exactly when it matters, and without evidence nobody can tell what went wrong.
TEJAS-CV sits around an existing computer-vision pipeline. It does not replace the pipeline and it does not retrain anything. It independently examines the dataset, the model, the inference outputs and the contributors, fuses the evidence into an explainable risk score, returns ACCEPT, REVIEW or QUARANTINE, and seals every decision into a signed, hash-chained audit ledger.
Everything runs offline. There are no network calls at runtime.
| Step | What TEJAS-CV does |
|---|---|
| Detect | Four parallel engines screen the data, the model, the provenance and the distribution shift |
| Explain | Every finding carries a reason, evidence, confidence, severity and a recommendation |
| Prove | SHA-256 fingerprints, Merkle proofs, Ed25519 signatures and a hash-chained ledger, checkable by an independent verifier |
| Decide | Evidence fusion returns ACCEPT, REVIEW or QUARANTINE with the rules that fired |
| Scenario | What happens | Verdict |
|---|---|---|
| Trusted pipeline | Clean batch, approved model, valid provenance | ✅ ACCEPT |
| Dataset poisoning | Trigger-patch poisoning, label conflicts, files altered after signing | ⛔ QUARANTINE |
| Model substitution or backdoor | A validly signed model hiding a trigger backdoor | ⛔ QUARANTINE |
| Silent weight tampering | Same architecture, modified weights | ⛔ QUARANTINE |
| Inference tampering | A stored inference output edited after the fact | ⛔ QUARANTINE |
| Distribution shift | Night, haze or blur batch | |
| Malformed or malicious upload | Path traversal, no images, malicious pickle, disallowed file type | 🚫 REJECTED at ingestion |
| Replayed inference record | A valid record presented a second time | 🚫 REJECTED |
- Data Integrity: unreadable files, exact and near duplicates, cross-label conflicts, class-distribution shift, label consistency, recurring trigger-artifact screening, per-label outliers, contributor attribution
- Model Integrity: pickle scan, signed trusted-registry comparison, structural inspection, behavioural fingerprint, controlled trigger testing with attack-success-rate reporting
- Provenance and Tamper Evidence: Merkle re-hash of datasets, signed manifests, contributor signatures, inference chain and ledger verification
- Shift and Drift: out-of-distribution rate, MMD permutation test, KS tests and named environmental conditions
- SHA-256 per file and a Merkle root per dataset
- Ed25519-signed, hash-chained audit blocks
- Signed assurance reports, training records and benchmark results
- An independent verifier that imports no TEJAS-CV code
- Six reproducible attack scenarios with an Execute all option and live step highlighting
- A real CNN trainer (NumPy backpropagation, exported to ONNX), optionally trained on poisoned data
- Benchmark suites scored against signed answer keys
- Synthetic test packs with known outcomes: ACCEPT, REVIEW, QUARANTINE and REJECTED
- Request-access flow with administrator approval
- Three roles: Admin, Operator and Client
- Every upload, training run, assessment and inference is attributed to the account that performed it
- Live pipeline view driven by WebSocket events
- Provenance graph showing people, data sources, engines, the decision and the sealed block, with timestamps
- Per-image Properties dialog: General, Digital Signatures, Security, Details and Previous Versions
- Command-centre dark interface and a public landing page
flowchart LR
A["Load"] --> B["Fingerprint"] --> C["Extract features"] --> D["Load model"] --> E["Parallel analysis"] --> F["Fuse evidence"] --> G["Decide"] --> H["Seal in ledger"] --> I["Signed report"]
| Engine | Examines | Example findings |
|---|---|---|
| Data Integrity | Dataset files, labels, statistics | Conflicting duplicates, recurring trigger artifact, label inconsistency, suspicious contributor |
| Model Integrity | Model file and behaviour | Pickle with dangerous imports, weights modified, substitution, trigger anomaly |
| Provenance and Tamper | Hashes, signatures, chains | Manifest mismatch, files changed after signing, altered inference record, compromised ledger |
| Shift and Drift | Distribution versus a clean baseline | High out-of-distribution rate, MMD shift, night, haze or blur conditions |
Engine scores are combined with noisy-OR, then weighted. Thresholds: ACCEPT below 35, REVIEW from 35 to 69, QUARANTINE from 70, plus explicit rules that are always reported:
| Rule | Effect |
|---|---|
| R1 | Any critical evidence leads to QUARANTINE |
| R2 | A high-confidence model or provenance integrity failure leads to QUARANTINE |
| R3 | Two or more high data findings lead to QUARANTINE (one leads to REVIEW) |
| R4 | A model without a trusted fingerprint is never auto-accepted (at least REVIEW) |
| R5 | Significant distribution shift leads to REVIEW |
| R6 | Drift alone never causes QUARANTINE |
Confidence is reduced when some checks could not run (coverage).
flowchart TD
A["👤 Admin / Operator / Client"] --> B["⚛️ React + Vite Client"]
B --> C["📊 Dashboard and Live Pipeline"]
B --> D["🧪 Attack Lab and Benchmarks"]
B --> E["🗂️ Assets, Workspace and Properties"]
B --> F["🧾 Audit Ledger and Provenance Graph"]
C --> G["📡 REST and WebSocket"]
D --> G
E --> G
F --> G
G --> H["🔐 Auth Middleware and Role Policy"]
H --> I["🖥️ FastAPI Application"]
I --> J["🎛️ Orchestrator - 10 stage pipeline"]
J --> K1["🗃️ Data Engine"]
J --> K2["🧩 Model Engine"]
J --> K3["🔗 Provenance Engine"]
J --> K4["🌦️ Drift Engine"]
K1 --> L["⚖️ Evidence Fusion"]
K2 --> L
K3 --> L
K4 --> L
L --> M["✅ ACCEPT / ⚠️ REVIEW / ⛔ QUARANTINE"]
M --> N["🔏 Signed Report"]
M --> O["⛓️ Hash-Chained Audit Ledger"]
I --> P[("SQLite - tejas.db")]
O --> P
I --> Q[("Files on disk - datasets, models, keys, reports")]
sequenceDiagram
participant User
participant Client
participant API
participant Orchestrator
participant Engines
participant Ledger
User->>Client: Select dataset, model and baseline
Client->>API: POST /api/jobs
API-->>Client: Job created with WebSocket URL
Client->>API: Connect to /ws/jobs/job_id
API->>Orchestrator: Run job
Orchestrator->>Orchestrator: Fingerprint and extract features
Orchestrator->>Engines: Run 4 engines in parallel
Engines-->>Client: Live progress events
Engines-->>Orchestrator: Findings with evidence
Orchestrator->>Orchestrator: Fuse evidence, apply rules
Orchestrator->>Ledger: Seal decision as signed block
Orchestrator-->>Client: Decision, risk, confidence, audit block
User->>Client: Open evidence or download signed report
Client->>API: GET report
API-->>Client: Signed JSON report with signature check
| Layer | Technology | Purpose |
|---|---|---|
| Backend | Python 3.10+, FastAPI, Uvicorn | REST and WebSocket API |
| Data | SQLAlchemy, SQLite (WAL) | Embedded database, no server required |
| Analysis | ONNX Runtime, NumPy, SciPy | Model inference, statistics, CNN trainer |
| Images | Pillow, ImageHash | Image handling and perceptual hashing |
| Trust | cryptography (Ed25519), SHA-256, Merkle trees |
Signatures, fingerprints, inclusion proofs |
| Accounts | scrypt (standard library) | Password hashing |
| Frontend | React, TypeScript, Vite | User interface and build tooling |
| UI libraries | Tailwind CSS, Framer Motion, Recharts, React Flow, TanStack Query | Styling, motion, charts, provenance graph, data fetching |
| Testing | pytest | Backend test suite |
Optional: PyTorch for TorchScript models, and DINOv2 (local weights) for stronger embeddings.
tejas-cv/
│
├── backend/
│ ├── app/
│ │ ├── api/ # REST and WebSocket routers
│ │ ├── auth/ # Password hashing, sessions, role policy, middleware
│ │ ├── core/ # hashing, merkle, keys, ledger, events, actor
│ │ ├── engines/ # data, model, provenance, drift, behaviour probes
│ │ ├── fusion/ # Evidence fusion and decision rules
│ │ ├── adapters/ # ONNX, TorchScript, pickle scanner, detection adapter
│ │ ├── features/ # Image statistics and embedders
│ │ ├── formats/ # CIFAR-10, GTSRB, corruptions, COCO, YOLO
│ │ ├── attacks/ # Dataset, model and inference attack generators
│ │ ├── ml/ # NumPy CNN, trainer, demo data
│ │ ├── benchmarks/ # Metrics (AUROC, TPR at fixed FPR)
│ │ ├── orchestrator/ # 10-stage job pipeline
│ │ ├── services/ # ingestion, baseline, inference, jobs, demo
│ │ ├── demo/ # Reproducible attack-lab data generator
│ │ ├── config.py
│ │ ├── database.py
│ │ └── main.py
│ │
│ ├── scripts/ # Command-line tools (see Section 10.3)
│ ├── tests/ # pytest suite
│ ├── benchmarks/ # catalog.yaml (public dataset catalogue)
│ ├── data/ # ⚠ GENERATED at runtime, NOT in git (see Section 8)
│ ├── requirements.txt
│ ├── requirements-dev.txt
│ └── Dockerfile
│
├── frontend/
│ ├── public/
│ ├── src/
│ │ ├── api/ # API client, endpoints, query keys
│ │ ├── realtime/ # Shared event socket and job stream
│ │ ├── components/
│ │ ├── pages/
│ │ ├── landing/ # Public landing page
│ │ └── types/
│ ├── package.json
│ └── vite.config.ts
│
├── docs/
│ └── banner.svg # Animated README banner
│
├── start-backend.ps1
├── start-frontend.ps1
├── docker-compose.yml
└── README.md
backend/data/is created on first run and is listed in.gitignore, so a fresh clone has no datasets, models, accounts or database. Section 8 explains how to generate all of it.
⚠️ A fresh clone contains code only. The datasets, models, database and signing keys live inbackend/data/, which is not stored in git. Follow step 7.4 below and Section 8 to create the data. The minimum is one command:python scripts/run_demo.py clean.
| Requirement | Version |
|---|---|
| Python | 3.10 or newer (64-bit) |
| Node.js | 20 or newer (current LTS recommended) |
| Git | any recent version |
git clone https://github.com/LoganthP/SIH2026.git tejas-cv
cd tejas-cvWindows (PowerShell):
cd backend
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements-dev.txtIf PowerShell blocks the activation script, run once: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
Linux / macOS:
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txtStill inside backend/ with the virtual environment active:
python scripts/run_demo.py cleanThis creates the database, the signing keys, the Attack Lab datasets and models, and runs one clean assessment (about 10 seconds). For the full set of data and all options, see Section 8.
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000Expected output:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Application startup complete.
The module is
app.main:app(notapp:app). Avoid--reloadduring demos, because each restart drops live connections. On Windows you can start the backend from the repository root with.\start-backend.ps1.
Interactive API documentation: http://127.0.0.1:8000/docs
In a second terminal:
cd frontend
npm install
npm run devOpen http://127.0.0.1:5173. The Vite dev server proxies /api and /ws to the backend, so no extra configuration is needed. On Windows: .\start-frontend.ps1.
- Open
http://127.0.0.1:5173and choose Request access. - Create the first account. The first account ever created becomes the administrator. Every later sign-up is a pending request that an administrator approves.
- Sign in. Assets and Registry now lists the demo datasets and models created in step 7.4. If you skipped that step, open Attack Lab and press Initialise Attack Lab instead.
- Run the scenarios in Attack Lab, or use the split button to Execute all remaining steps.
Locked out? Create or reset an administrator from the command line:
cd backend
python scripts/create_admin.py --username your.namedocker compose up --buildThe container starts with an empty data volume. Create the data inside it, for example docker compose exec backend python scripts/run_demo.py clean.
cd frontend
npm run buildServe the dist/ folder from any static file server. No external assets are required, so it works offline. Set VITE_API_URL when the API is on a different origin.
backend/data/ holds the SQLite database, the private signing keys, uploaded datasets, models and signed reports. It is large, specific to one installation and sensitive, so it is git-ignored on purpose. Everything in it can be regenerated with the commands below.
Each installation creates its own Ed25519 signing key on first start. Reports and ledgers produced on one machine will not verify against another machine's key. Never commit backend/data/keys/.
Location in backend/data/ |
Created by | Contents |
|---|---|---|
tejas.db |
First start of the API or any script | Accounts, assets, jobs, findings, audit ledger |
keys/ |
First start | Platform Ed25519 key pair |
demo_src/, datasets/, models/, baselines/ |
Attack Lab initialisation | 4 synthetic datasets (reference, clean batch, poisoned batch, drift batch), 3 ONNX models (approved, vendor update with a hidden backdoor, patched weights), 3 contributors, a reference baseline, and the approved-model registry entry aerial-landcover-v1 |
reports/ |
Every assessment | Signed JSON reports |
training_data/, trained_models/ |
train_model.py |
Synthetic training set, trained ONNX models and signed training records |
test_packs/<scale>/ |
test_packs.py |
Upload-ready archives and models with an answer key (expected.json) and scored results |
benchmarks/ |
run_benchmark.py |
Signed results.json and results.md |
benchmarks_raw/ |
fetch_datasets.py |
Downloaded public archives with their SHA-256 |
Stop the backend first (so two processes never write at once), then run from backend/ with the virtual environment active:
python scripts/run_demo.py clean
python scripts/train_model.py --demo-data --name cnn-clean --trust aerial-cnn-v1
python scripts/train_model.py --demo-data --name cnn-trojan --poison-target water --poison-position bottom-right --poison-pattern white
python scripts/test_packs.py all --scale small
python scripts/run_benchmark.py --suite ciThis takes about two minutes and uses roughly 280 MB of disk. Then start the backend and the frontend as in Section 7. The commands are identical in PowerShell, bash and zsh.
| Command | Time | Required? |
|---|---|---|
run_demo.py clean |
about 10 s | Yes, this is the minimum |
train_model.py (twice) |
about 15 s each | Optional: real trained models, one approved and one backdoored |
test_packs.py all --scale small |
about 1 min | Optional: 24 known-answer cases for the Workspace |
run_benchmark.py --suite ci |
about 30 s | Optional: a signed benchmark report |
python scripts/run_demo.py cleanCreates the database, keys, four datasets, three models, the baseline and one clean assessment, and prints the verdict. It is safe to run again: if the lab exists, it is reused.
You can also do this from the UI: sign in as an administrator, open Attack Lab and press Initialise Attack Lab. Both routes create the same data.
To also run all six scenarios from the terminal:
python scripts/run_demo.pyExpected verdicts: A ACCEPT · B QUARANTINE · C QUARANTINE · C2 QUARANTINE · E REVIEW · D QUARANTINE, followed by Audit ledger: ... VALID. Scenario D deliberately edits one inference record, so the independent verifier will report it afterwards (see 8.6).
# An honest model, approved into the trusted registry
python scripts/train_model.py --demo-data --name cnn-clean --trust aerial-cnn-v1
# A model trained on poisoned data, with a trigger of your choice
python scripts/train_model.py --demo-data --name cnn-trojan --poison-target water --poison-position bottom-right --poison-pattern whiteEach run trains a small CNN (NumPy backpropagation), exports it to ONNX, registers it, writes a signed training record and prints the measured accuracy. For the poisoned model it also prints the measured attack success rate.
Poison options: --poison-position top-left | top-right | bottom-left | bottom-right | center · --poison-pattern white | black | checker | red | yellow · --poison-rate 0.10 · --poison-target <class>.
Audit a trained model from the terminal:
python scripts/audit.py --model MDL-XXXXXXXXXX # zero-trust
python scripts/audit.py --model MDL-XXXXXXXXXX --trusted aerial-cnn-v1 # against the approved modelThe model id is printed when the model is registered, and shown in Assets and Registry. To train on your own ingested dataset, use --dataset DS-XXXXXXXXXX --input-size 32 instead of --demo-data.
python scripts/test_packs.py all --scale smallGenerates upload-ready archives and models with known correct outcomes (ACCEPT, REVIEW, QUARANTINE and REJECTED), ingests and analyses each case, scores it against the answer key, and writes a signed report to backend/data/test_packs/small/results.md.
| Scale | Images per dataset | Approx. archive size |
|---|---|---|
small |
200 | 70 MB |
medium |
1,000 | 350 MB |
large |
2,000 | 700 MB |
xl |
5,000 | 1.7 GB |
To generate the archives without analysing them, use generate instead of all, then ingest them yourself:
- Workspace → Data → Test pack → Ingest this case (operator or admin), or
- Workspace → Data → My upload with any archive from
backend/data/test_packs/<scale>/datasets/.
At the small scale, 22 of 24 cases match the expected outcome. The two misses are reported honestly in results.md: a class-skewed batch is accepted instead of reviewed, and a signed 10% trigger-poisoning batch is sent to REVIEW instead of QUARANTINE.
python scripts/run_benchmark.py --suite ci # about 30 seconds
python scripts/run_benchmark.py --suite smoke # about 2 minutesRuns real attacks with known answers through the full pipeline and scores the platform against them. Results are written to backend/data/benchmarks/<suite>/<timestamp>/ as signed results.json and a readable results.md that ends with a Known weak spots section.
Two stages, so that the machine running TEJAS-CV never needs internet access.
On a connected machine: download
python scripts/fetch_datasets.py --list # show the catalogue
python scripts/fetch_datasets.py yolo_coco8 # download oneArchives are saved to backend/data/benchmarks_raw/<id>/ together with their SHA-256. Copy that folder to the offline machine if needed.
On the TEJAS-CV machine: import
python scripts/import_dataset.py yolo_coco8The importer verifies the archive hash, converts the data into the internal layout, registers it (per-file SHA-256 and a Merkle root), stores the licence information, and seals a DATASET_IMPORTED block in the ledger. It prints the new dataset id.
| Id | Dataset | Import command |
|---|---|---|
yolo_coco8 |
COCO8, 8 images with boxes (YOLO format) | python scripts/import_dataset.py yolo_coco8 |
cifar10 |
CIFAR-10, binary version | python scripts/import_dataset.py cifar10 --subset 2000 |
gtsrb |
German Traffic Sign Recognition Benchmark | python scripts/import_dataset.py gtsrb --subset 2000 |
cifar10_c |
CIFAR-10-C corruptions | python scripts/import_dataset.py cifar10_c --corruption fog --severity 3 --subset 1000 |
coco2017_val |
COCO 2017 validation images and boxes (large) | python scripts/import_dataset.py coco2017_val --subset 500 |
Use --archive <path> when the archive is already on disk, and --seed for a reproducible subset.
Pin the hash. The first import prints the archive's SHA-256. Paste it into the matching archive_sha256 field of backend/benchmarks/catalog.yaml. Every later import is then checked against it, and a mismatch is refused before anything is extracted.
Manual sources. backdoorbench, trojai_round0 and imagenet_c need a registration or licence click-through (ImageNet-C is research-only). Download them yourself, convert models to ONNX in an isolated environment (never unpickle them inside TEJAS-CV), and bring them in with Workspace → Model → Upload, or python scripts/import_dataset.py <id> --folder <path-to-class-folders> for image data.
The COCO8 import path was exercised end to end. The other converters are covered by the test suite using small generated samples, but their full public archives were not downloaded for this README.
In the UI: Workspace → Data → My upload (operator or admin). Choose the format and upload a .zip:
archive.zip
├── class_a/
│ ├── img_001.png
│ └── img_002.png
├── class_b/
│ └── img_001.png
├── manifest.json # optional, signed by the contributor
└── manifest.sig # optional
- Classification: one folder per class, as above.
- YOLO:
images/,labels/and an optionaldata.yaml. - COCO:
images/andannotations.json.
Every upload is checked at the ingestion gate (path traversal, symlinks, no images, unreadable files) and gets a Merkle root, per-image hashes and a ledger entry. A rejected upload is sealed in the ledger with its hash and reason.
To make an uploaded dataset the reference for drift checks, fit a baseline on clean data of the same domain:
python scripts/calibrate.py --dataset DS-XXXXXXXXXX --holdout DS-YYYYYYYYYY --name "my clean baseline"python scripts/verify_independent.py --rehash-filesAfter Step 1 alone, the output ends with RESULT: everything verifies. In the UI, confirm:
- the top bar shows SYSTEM SECURE
- Assets and Registry lists the demo datasets and models
- the Dashboard counters are non-zero
- Audit Ledger → Verify entire chain reports the chain as valid
- Deliberate tampering is part of the demo. Attack Lab scenario D and the benchmark suites edit one inference record on purpose, to prove detection. Afterwards the independent verifier reports
inference #N: output altered. This is expected. A scenario edit can be undone from the UI (Restore demo edits or the Restore button beside the record in the Audit Ledger's inference-chain panel); the benchmark's edit cannot, so run benchmarks last, or re-create the data (8.7). - A compromised ledger quarantines everything. If the audit ledger is flagged (for example after Attack Lab Step 6), every new assessment is QUARANTINE by design. Use Restore demo edits first.
- Scripts act as your operating-system user. Records created from the command line are attributed to
local:<your-username>and appear as CLI in the provenance graph. - Seed with the backend stopped. The lab bootstrap is thread-safe inside one process, not across two. Seeding first and starting the API afterwards avoids any clash.
| Goal | How |
|---|---|
| Reset the lab | Attack Lab → Reset lab, or python scripts/run_demo.py --reset. |
| Completely fresh start | Stop the backend, delete backend/data/, then repeat Section 8.3. A new signing key is generated. |
| Recover access after a reset | python scripts/create_admin.py --username your.name |
| Back up or move an installation | Copy the whole backend/data/ folder, including keys/, to the same location in another copy of the repository. Signatures keep verifying because the key travels with the data. |
All settings are optional.
| Variable | Default | Description |
|---|---|---|
TEJAS_HOME |
backend/data |
Where the database, keys, datasets, models and reports are stored |
TEJAS_CORS |
http://localhost:5173,http://127.0.0.1:5173 |
Allowed frontend origins |
TEJAS_EMBEDDER |
auto |
auto, handcrafted or dinov2 |
TEJAS_MAX_SAMPLES |
2000 |
Maximum images analysed per job |
TEJAS_AUTH_REQUIRED |
1 |
Leave on. 0 exists only for automated tests |
TEJAS_DINOV2_REPO |
none | Local DINOv2 repository path |
TEJAS_DINOV2_WEIGHTS |
none | Local DINOv2 weights file |
VITE_API_URL |
http://127.0.0.1:8000 |
Frontend only: API origin for a production build |
TEJAS_HOMEmust be the same for the seeding scripts and the API. Set it in the same terminal before running either, or leave it unset to usebackend/data.
Using DINOv2 embeddings offline
- On a connected machine, clone
facebookresearch/dinov2and download thedinov2_vits14_pretrain.pthweights. - Copy both to the offline machine and set:
TEJAS_EMBEDDER="dinov2"
TEJAS_DINOV2_REPO="/path/to/dinov2"
TEJAS_DINOV2_WEIGHTS="/path/to/dinov2_vits14_pretrain.pth"- Rebuild the baseline. If DINOv2 is not configured, the built-in handcrafted embedder is used and every report says so.
Offline installation of Python packages
# On a connected machine
pip download -r requirements.txt -d wheelhouse
# On the offline machine
pip install --no-index --find-links wheelhouse -r requirements.txt- Attack Lab: initialise the lab (or run
python scripts/run_demo.py cleanbeforehand). - Run the six steps one by one, or Execute all remaining steps.
- Live Pipeline: watch the four engines run in parallel, then fusion, decision and the sealed audit block.
- Evidence: read the reason, evidence and recommendation behind each finding.
- Provenance: see who uploaded, trained, assessed and sealed what, with timestamps.
- Audit Ledger: after the simulated insider edit in Step 6, run Verify entire chain and Independent Verification.
- Press Restore demo edits to return to a healthy ledger.
The Workspace guides Data, Model, Assess and Insights:
- Data: upload your own archive, pick an existing dataset, ingest a generated test pack, or use the demo sets (see Section 8).
- Model: choose an existing model, train one on your dataset, or upload an ONNX or TorchScript file.
- Assess: pick a baseline and an optional trusted model, then run the assessment with the live pipeline.
- Insights: decision, per-engine scores, flagged samples, drift analysis, provenance and a signed report.
Run from backend/ with the virtual environment active.
| Script | Purpose |
|---|---|
scripts/run_demo.py |
Create the Attack Lab data and run its scenarios, no server needed |
scripts/train_model.py |
Train a CNN (optionally with a poisoned trigger), register it, optionally approve it |
scripts/audit.py |
Run one assessment and print the verdict with evidence |
scripts/make_attack.py |
Generate dataset or model attacks with a signed answer key |
scripts/run_benchmark.py |
ci, smoke and standard suites scored against ground truth |
scripts/test_packs.py |
Generate synthetic packs covering ACCEPT, REVIEW, QUARANTINE and REJECTED |
scripts/calibrate.py |
Fit a baseline on clean data and measure false alarms on a hold-out set |
scripts/fetch_datasets.py |
Download public datasets (internet-connected machine only) |
scripts/import_dataset.py |
Offline, hash-verified import of CIFAR-10, GTSRB, CIFAR-10-C, COCO and YOLO data |
scripts/verify_independent.py |
Independent re-verification of the ledger, inference chain, files and reports |
scripts/create_admin.py |
Create or reset an administrator |
Full interactive documentation: http://127.0.0.1:8000/docs
| Area | Endpoints |
|---|---|
| Accounts | POST /api/auth/signup · POST /api/auth/login · POST /api/auth/logout · GET /api/auth/me |
| Assets | POST /api/assets/datasets · POST /api/assets/models · GET /api/assets |
| Assessments | POST /api/jobs · GET /api/jobs/{id}/summary · /findings · /report · /provenance-graph |
| Live updates | WS /ws/jobs/{id} · WS /ws/events |
| Inference | POST /api/inference · POST /api/inference/attest · POST /api/inference/verify-chain |
| Ledger | GET /api/audit · POST /api/audit/verify |
| Training | POST /api/ml/train |
| Benchmarks | POST /api/benchmarks/runs · GET /api/benchmarks/results |
| Lab | POST /api/demo/bootstrap · POST /api/demo/scenarios/{name} |
| System | GET /api/system/status · POST /api/system/verify-independent |
flowchart LR
A["👤 Request access"] --> B["🛂 Admin approval"]
B --> C["🔑 Login - scrypt verified"]
C --> D["🍪 HttpOnly session cookie"]
D --> E["🛡️ Auth middleware"]
E --> F["📜 Role policy: client / operator / admin"]
F --> G["🔌 API routes and WebSockets"]
G --> H["📥 Ingestion gate"]
H --> H1["Zip-slip and symlink rejection"]
H --> H2["Pickle scan before any model load"]
H --> H3["File type allow-list"]
G --> I["⛓️ Signed hash-chained ledger"]
I --> J["🔍 Independent verifier"]
| Capability | Admin | Operator | Client |
|---|---|---|---|
| View dashboards, evidence, provenance, ledger | ✅ | ✅ | ✅ |
| Run assessments, run inference, verify chains | ✅ | ✅ | ✅ |
| Ingest datasets, upload models, build baselines | ✅ | ✅ | ❌ |
| Train models, run benchmarks | ✅ | ✅ | ❌ |
| Approve a model as trusted | ✅ | ❌ | ❌ |
| Attack Lab, tamper, restore and reset tools | ✅ | ❌ | ❌ |
| Manage users and approve access | ✅ | ❌ | ❌ |
- Default deny: every write route requires an administrator unless it is explicitly allowed for a lower role
- Sessions: random 256-bit tokens, and only their SHA-256 is stored on the server
- Lockout: five failed logins lock an account for five minutes
- Passwords: at least 10 characters, letters and digits, and not containing the username
- Separation of duties: operators can ingest and train, but only administrators can approve a model as trusted
- Ingestion gate: archives are checked for path traversal and symlinks, model files for dangerous pickle imports, and uploads against a file-type allow-list
- Evidence for rejections: every rejected upload is sealed in the ledger with its file hash and the reason
cd backend
pytest -qThe suite covers cryptography, ledger tamper detection, every attack scenario, the API and WebSocket contract, roles and access policy, training, benchmarks and sample properties. It uses its own temporary data folder, so it never touches backend/data/.
Frontend checks:
cd frontend
npm run build
npm run lintEvery benchmark row is a real pipeline run scored against a signed answer key:
- Sample-level precision and recall for poisoned files
- Model-level backdoor detection (AUROC, TPR at 5% FPR)
- Drift verdicts and named-condition accuracy
- Inference edit and replay detection
- A generated Known weak spots section
Attacked files get neutral names, the answer key is stored outside the dataset, and baselines are fitted on a separate clean split. Commands are in Section 8.4, Step 4.
TEJAS-CV audits models, so it does not need to train them. For demos and benchmarks it includes a small CNN trainer so the models under test are genuinely trained:
- NumPy backpropagation with Adam, exported to standard ONNX
- Gradients unit-tested against finite differences, and ONNX output checked against NumPy
- Optional BadNets-style poisoning with a chosen trigger, with the measured attack success rate recorded
- A signed training record sealed into the ledger for every run
python scripts/verify_independent.py --rehash-filesRe-checks the ledger, inference chain, stored files and signed reports using only the standard library, the cryptography package and the platform's public key. It can also be run from the Audit Ledger page.
Common problems and fixes
| Problem | Fix |
|---|---|
| Assets page is empty, or the Workspace has nothing to pick | A fresh clone has no data. Run python scripts/run_demo.py clean (Section 8) |
| The Test pack card shows no cases | Generate them: python scripts/test_packs.py generate --scale small |
Error loading ASGI app. Attribute "app" not found |
Use app.main:app and run from inside backend/ |
ModuleNotFoundError: app |
You are not in the backend folder |
ModuleNotFoundError: yaml (or another package) |
Activate the virtual environment, then pip install -r requirements-dev.txt |
| Port 8000 already in use | Add --port 8001 and update VITE_API_URL |
onnxruntime fails to install |
Use a 64-bit Python 3.10 or newer |
| Frontend shows 401 or "login required" | Sign in, or create the first account |
| Accounts disappeared after a reset | Expected: Reset lab and --reset wipe the whole database. Re-create the admin with python scripts/create_admin.py --username your.name |
| Every assessment is QUARANTINE | The audit ledger is flagged as compromised (for example after a demo tamper). Use Restore demo edits or Reset lab |
Independent verifier says inference #N: output altered |
A scenario or benchmark edited that record on purpose (Section 8.6) |
SECURITY ERROR: Archive hash mismatch on import |
The file does not match the hash pinned in catalog.yaml. Re-download it, or clear the pin if you trust the new file |
catalog.yaml has no archive_sha256 on import |
A notice, not an error. Paste the printed hash into catalog.yaml to pin it |
COCO8 import prints 30 boxes (30 invalid) |
Cosmetic: the official coco8.zip has no class-name file, so every class id counts as unknown. The stored boxes are valid |
WinError 145 while building the lab |
Close any Explorer window open inside backend\data, then retry |
| Scripts and the API show different data | They used different TEJAS_HOME values. Use the same one for both |
- Automated backend test suite (pytest), including tamper detection, role enforcement and every attack scenario
- The independent verifier detects direct edits made to the database
- Ed25519 signatures verify with OpenSSL
- Data setup reproduced from an empty folder: Attack Lab, trained models, test packs, benchmark and a public-dataset import
- Fully offline operation
- Trigger testing screens a fixed bank of patch triggers. In our own benchmark, a learned backdoor with a red centre patch was caught only by comparing against the approved model, not by zero-trust screening alone. Blended and signal-style triggers are also weak spots.
- The ledger is blockchain-style: signed, hash-chained blocks with Merkle roots on a single node. It makes tampering evident and provable, but it is not a distributed consensus network.
- Demo data is synthetic by design so that results are reproducible. Thresholds were set on it and should be recalibrated on real reference data.
- The default embedder is handcrafted. Stronger results need local DINOv2 weights.
- The signing key is stored locally. A production deployment should keep it in an HSM or KMS.
- Reset lab clears user accounts along with the lab data.
- Drift results indicate operational change, not malicious intent.
- Trigger reverse-engineering to catch triggers outside the fixed bank
- DINOv2 embeddings by default
- Real-data benchmark reports (CIFAR-10, GTSRB, COCO)
- Full object-detection assurance (YOLO and COCO engines)
- Hardware-backed key management
- Periodic export of the ledger head to write-once media
- Reset that preserves user accounts
- Optional PostgreSQL backend for multi-user deployments