Built by OneHackers for Cyber Carnival 2K26 | VIT-B
Team: Shanzal Firoz (Lead),
Navni Danwani,
Aditya Vishwakarma
Warning
Do Not git clone This Repository.
The GitHub repository (github.com/shanzal-vitb/dropnode) is connected to the live web demo and may contain dev artifacts or demo-specific changes not intended for self-hosted use. Always download source from the Releases page instead.
DropNode is a self-hosted, browser-based file upload portal that scans every uploaded file against the VirusTotal multi-engine API before making it available for download. Safe files are moved to permanent storage; unsafe files are quarantined and automatically deleted after three minutes. Every scan is persisted to a local SQLite database and can be retrieved at any time via a unique Drop ID.
dropnode/
├── app.py # Flask backend — routes, scanning logic, DB, deletion scheduler
├── index.html # Single-page frontend (Tailwind CSS + vanilla JS)
├── requirements.txt # Python dependencies
├── .api_key # Persisted VT API key (chmod 600, auto-created by Set API modal)
├── dropnode.db # SQLite database (auto-created on first run)
├── static/
│ ├── assets/ # SVG icons and logos (file-based / CDN URL references)
│ └── fonts/ # Custom font files (SamsungSharpSans, PPSupplySans)
├── cache/ # Temporary file storage (pre-scan, auto-created)
├── uploads/ # Permanent safe file storage (post-scan, auto-created)
├── demo/ # Demo build — same codebase + warning banner [V1.1]
└── venv/ # Python virtual environment (not committed to version control)
- Python 3.10 or later
- A VirusTotal API key (free tier available at https://www.virustotal.com)
- Internet access (for VirusTotal API calls)
Important
Do not git clone this repository. Download the latest source archive from the Releases page.
It is strongly recommended to run DropNode inside a Python virtual environment to avoid dependency conflicts.
Linux / macOS:
python3 -m venv venv
source venv/bin/activateWindows (Command Prompt):
python -m venv venv
venv\Scripts\activate.batWindows (PowerShell):
python -m venv venv
venv\Scripts\Activate.ps1You should see (venv) prefixed in your terminal prompt once the environment is active.
pip install -r requirements.txtGet a free API key from https://www.virustotal.com/gui/my-apikey after registering.
Option A — In-browser via "Set API" modal (V1.1) [recommended]:
Start the app, then click "Set API" in the header nav. Enter your key in the popup — it is saved to .api_key (chmod 600) and persists across server restarts. No restart required.
Option B — Environment variable:
Linux / macOS:
export VT_API_KEY="your_api_key_here"Windows (Command Prompt):
set VT_API_KEY=your_api_key_hereWindows (PowerShell):
$env:VT_API_KEY="your_api_key_here"Option C — Edit app.py directly:
Open app.py and replace the placeholder on the relevant line:
VIRUSTOTAL_API_KEY = 'your_api_key_here'Key resolution order: .api_key file → VT_API_KEY env var → hardcoded value in app.py.
python app.pyThe server starts on http://localhost:5000 in debug mode. Open that URL in your browser.
To deactivate the virtual environment when done:
deactivateThe file is sent to the /upload endpoint via an XHR request with real-time progress tracking. It is saved to the cache/ directory and its SHA-256 and MD5 hashes are computed immediately. File metadata (name, size, type, hashes) is displayed to the user before scanning begins.
On user confirmation, the file is submitted to the VirusTotal API v3 (/files endpoint). The backend polls the analysis endpoint until a result is ready, then aggregates the engine verdicts into a single result object containing:
- Threat status (Clean / Suspicious / Malicious)
- Risk score (0–100) and risk badge (Low / Medium / High)
- Detection ratio (flagged engines / total engines)
- Malware signature name and threat category
- List of flagging engines
- Full file metadata from VirusTotal (MIME type, file type, publisher, certificate validity)
A unique 20-character uppercase alphanumeric Drop ID is generated for each scan and stored in the database.
| Verdict | Action |
|---|---|
| Safe | File is moved from cache/ to uploads/. Download becomes available. |
| Unsafe | File remains in cache/ and is scheduled for deletion after 3 minutes. Download is disabled. |
The deletion is handled by a background daemon thread. The timer starts at the moment of the first scan and is never reset, even if the file is re-scanned. The frontend chip updates in real-time from "File will be deleted in 3 minutes" to "File has been deleted" once the timer expires.
Past scans can be retrieved by Drop ID or filename via the search bar. Results are fetched from the SQLite database and persist across server restarts.
A full HTML scan report can be downloaded at any time for any scan result, referenced by Drop ID or filename. The report includes all scan metadata, engine results, and file hashes — styled with the same custom fonts as the main webapp.
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Serves the main web UI |
| POST | /upload |
Receives a file, saves it to cache/, returns hashes and metadata |
| POST | /scan |
Submits cached file to VirusTotal, returns full scan result |
| GET | /file-status?filename=<name> |
Returns real-time deletion status for a cached file (pending, seconds_remaining, exists) |
| GET | /search?q=<query> |
Searches scan history by Drop ID or filename (up to 20 results) |
| GET | /download/<filename> |
Downloads a safe file from uploads/; returns 410 Gone if file_deleted=1 |
| GET | /report/<upload_id> |
Downloads a full HTML scan report by Drop ID or filename |
| POST | /set-api-key |
[V1.1] Accepts VT API key at runtime; persists to .api_key (chmod 600) |
| POST | /delete-file |
[V1.1] Removes physical file, sets file_deleted=1; preserves scan history |
| POST | /delete-result |
[V1.1] Hard-purges: removes file AND DB row |
| POST | /cleanup-cache |
[V1.1] Beacon endpoint; cleans orphaned cache/ files on page unload |
Scan results are stored in dropnode.db (SQLite). The scan_results table schema:
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Auto-incremented primary key |
upload_id |
TEXT | Unique 20-character Drop ID |
filename |
TEXT | Sanitized filename |
is_safe |
INTEGER | 1 = safe, 0 = unsafe |
threat_status |
TEXT | Clean / Suspicious / Malicious |
risk_score |
INTEGER | 0–100 |
risk_badge |
TEXT | Low / Medium / High |
detection_ratio |
TEXT | e.g. "3/72" |
engines_scanned |
INTEGER | Total AV engines queried |
malware_signature |
TEXT | Signature name from flagging engine |
threat_category |
TEXT | Detection method / category |
flagged_engines |
TEXT | JSON array of engine names |
file_type |
TEXT | Human-readable file type |
file_size |
INTEGER | Size in bytes |
mime_type |
TEXT | MIME / type tag from VirusTotal |
sha256 |
TEXT | SHA-256 hash |
md5 |
TEXT | MD5 hash |
digital_signature |
TEXT | Signature validity status |
publisher |
TEXT | Publisher subject from cert info |
cert_validity |
TEXT | Valid / Not Signed / Unverified |
file_created |
TEXT | File creation timestamp |
last_modified |
TEXT | File modification timestamp |
scan_timestamp |
TEXT | UTC timestamp of scan |
created_at |
TEXT | DB row creation timestamp |
file_deleted |
INTEGER | [V1.1] 1 if file manually deleted; 0 otherwise |
| Constraint | Value |
|---|---|
| Maximum file size | 32 MB |
| VirusTotal free tier rate limit | 4 req/min, 500/day |
| Unsafe file auto-deletion | 180 seconds after first scan |
| Large file scan time | Up to 5 minutes (VirusTotal queue) |
| Search results returned | Up to 20 per query |
| Drop ID length | 20 chars, uppercase alphanumeric |
- Filenames are sanitized using
werkzeug.utils.secure_filenamebefore being written to disk — applied at all endpoints including the new V1.1 deletion endpoints. - SHA-256 and MD5 hashes are computed locally with Python's
hashlibbefore any VirusTotal submission. - The
.api_keyfile is written withchmod 0o600(owner-read-only) — no world-readable API key exposure. - Unsafe files are isolated in
cache/and never moved touploads/; they are deleted by a daemon thread after 3 minutes. /download/<filename>checks bothis_safeandfile_deletedbefore serving; returns 410 Gone for manually deleted files.- VirusTotal API v3 provides multi-engine scanning across 70+ antivirus engines.
/cleanup-cacherefuses to delete files already on the auto-delete timer — prevents timer bypass via the beacon endpoint.
[NEW]#1 —/set-api-keyPOST endpoint: accepts VirusTotal API key at runtime, no restart required.[NEW]_load_api_key()— three-tier cascade:.api_keyfile →VT_API_KEYenv var → hardcoded fallback.[SEC]API key persisted to.api_keywithchmod 0o600(owner-read-only).[NEW]#4 —/delete-file: removes physical file fromuploads/, setsfile_deleted=1in DB, preserves scan history.[NEW]#4 —/delete-result: hard-purges file fromuploads/orcache/AND deletes the DB row.[NEW]#16 —/cleanup-cache:sendBeaconendpoint; cleans orphanedcache/files on page unload; skips files already on the auto-delete timer.[NEW]#16 —_startup_cache_sweep(): runs at import time; clears all orphanedcache/files from crashed or reloaded sessions.[NEW]file_deletedcolumn (INTEGER DEFAULT 0) added toscan_resultstable.[UPD]Zero-downtime DB migration:ALTER TABLE ADD COLUMN file_deletedruns automatically on existing DBs.[UPD]save_result()androw_to_dict()read/writefile_deleted; exposed as boolean in all API JSON responses.[FIX]#7 —/download/<filename>returns HTTP 410 Gone whenfile_deleted=1.[FIX]schedule_delete()is idempotent — re-scan cannot reset an already-running deletion timer.[UPD]DB connections use Flaskgcontext viaget_db()/close_db(), preventing connection leaks.[SEC]secure_filename(werkzeug) applied at all new deletion endpoints.
[NEW]#1 — "Set API" item added to desktop header nav (right of About) and mobile hamburger menu.[NEW]#1 — Clicking "Set API" opens a blurred-background popup with smooth fade+scale animation.[NEW]#1 — Password-type input with eye-toggle; tick-button in--accentcolour submits viasubmitApiKey().[NEW]#1 — On success: modal closes with reverse animation; success snackbar: "API key has been saved & Applied."[NEW]#1 — Modal dismisses on backdrop click and Escape key; input cleared after successful submission.[NEW]#4 — Delete button added right of Re-Scan in the results area.[NEW]#4 — Delete confirmation popup: "Delete File & Result" and "Delete File Only" for SAFE files.[NEW]#4 — UNSAFE delete popup: shows "Delete Result" only + note "File will be deleted in 3 mins automatically."[NEW]#5 — Download File button repositioned to immediately right of the Delete button.[NEW]#21 — Demo build warning banner fordemo/build.[NEW]#4 / #30 — Re-Scan button disabled with hover/tap tooltip when file manually deleted: "The file has been deleted by the user, re-upload it to re-scan."[NEW]crossDissolvePanel(data)— fade-out/update/fade-in animation for smooth in-place re-scan panel updates.[NEW]sendBeacon /cleanup-cachefires onbeforeunload, preventing orphaned temp files incache/.[NEW]Dedicated Search modal overlay with scoped clickable result cards.[NEW]showSnack({type, message, buttonLabel, onButton, duration})— unified toast system with stacking, auto-dismiss, and action buttons.
[FIX]#8 — System status pill no longer flashes "Checking..." every 10 s during keep-alive ping. "Checking" (yellow) only shown during genuine online ↔ offline transition.[FIX]#7 — Download button on search result cards no longer triggers the active scan result's download; scoped to each card's own Drop ID.[FIX]#29 — "Delete File Only" button disabled immediately after use; prevents repeated snackbar loop.[FIX]#10 — Unsafe delete popup note updates from "File will be deleted in 3 mins" → "File has been deleted automatically." in real-time when the timer fires.[FIX]#16 — Files incache/after a mid-scan page reload are cleaned up on page unload viasendBeacon.[FIX]Module-levelscanIntervalIdanddeleteTimerprevent duplicate polling loops across re-scan attempts.[FIX]resetAll()correctly clears all timers, intervals, XHR refs, and UI state on new upload.[FIX]#28 — Drop ID search box now centred correctly in the nav header.
[UPD]#2 — Tagline: "The node that protects" → "The node that protects." (full stop added).[UPD]#17 — Upload area heading: "Secure File Portal" → "Secure File Upload Portal".[UPD]#13 — Nav label and popup heading: "Settings" → "Set API" throughout.[UPD]#13 — GitHub profile URLs for all three team members confirmed and updated in the footer.[UPD]#13 — Build chip:Build: Beta, V1.1 - 250226.[UPD]#3 — Mobile brand logoclamp()minimum value increased for better small-screen legibility.[UPD]#6 — System status pill: yellow colour while in "Checking..." state.[UPD]#13 — Status pill text and size slightly reduced for a cleaner footer.[UPD]#9 — Delete confirmation popup buttons sized relative to text content for uniform appearance.[UPD]#11 — Buttons in the upload/scanning area wrap to left-aligned on narrow screens.[UPD]#12 — Buttons in the results area wrap to left-aligned on narrow screens.[UPD]#15 — Inline SVGs reverted to file-based / CDN URL references for easier maintenance.[UPD]#18 — HTML scan report: custom fonts (SamsungSharpSans / PPSupplySans via CDN) match main webapp.[UPD]#19 — HTML report generator updated to include the live DropNode webapp link.[UPD]#30 — After file-only deletion (safe file): "Delete File & Result" relabels to "Result Only".[UPD]#30 — Note "The file has been deleted by the user." appears below action description after file-only deletion.
[NEW]#14 — Separatedemo/folder created inside the V1.1 production directory:- #21 — Warning banner about demo usage and public file exposure risk.
- #22 — Build chip:
Build: Beta, V1.1 - 250226_demo
[UPD]Demo warning committed to thedropnodemain branch alongside all V1.1 changes.
Unsafe Or Malicious File Result
- The app runs with Flask's built-in development server (
debug=True) on port 5000. For production, use Gunicorn behind a reverse proxy (Nginx or Caddy). - Scan results use
INSERT OR REPLACEso re-scanning a file with the same Drop ID updates the existing row. - The deletion registry (
_pending_deletions) is in-memory. If the server restarts mid-timer, the timer is lost — the file will remain incache/until the next startup sweep (_startup_cache_sweep()) clears it. - The
.api_keyfile takes precedence over theVT_API_KEYenvironment variable, which in turn takes precedence over the hardcoded value inapp.py. - The Drop ID is generated independently of VirusTotal's
analysis_id— it is a local 20-character alphanumeric identifier unique within the local database.








