commit e6181d891adac703c8df14c677d7e3b713688cb1 Author: drjones Date: Fri Sep 25 07:08:49 2026 -0700 Cookie Vault v2: Netscape cookies.txt importer + one-click selenium login diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd3bc8a --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# COOKIE VAULT + +Netscape `cookies.txt` bulk importer + one-click login launcher. Runs on Windows (Commando VM601), Flask + SQLite, port 5066. + +## Features +- Drag-drop / folder-scan / paste import of hundreds of cookies.txt files +- `#HttpOnly_` line handling, tab or space separated, junk-tolerant parser +- One-click LOGIN: launches Chrome (selenium) with that domain's cookies injected into a persistent per-domain profile +- Search by domain or cookie name +- Export as JSON (browser-extension format) or Netscape .txt +- Idempotent imports (UNIQUE per domain+path+name+source_file) + +## Deploy (Windows) +``` +pip install flask selenium +C:\cookievault\app.py # the app +C:\cookievault\incoming\ # dump cookies.txt here, hit RESCAN +C:\cookievault\sessions\ # chrome profiles (auto-created) +``` +Autostart: HKLM `...\CurrentVersion\Run` key `CookieVault` -> `C:\cookievault\start_cookievault.bat` + +## Use +Open http://:5066 — drag files in, click IMPORT. Click LOGIN next to a domain to open a pre-authenticated Chrome on the host desktop. + +## Endpoints +- `POST /import` (multipart files=, or form paste=) +- `POST /scan` — rescan incoming folder +- `GET /open?domain=X` — launch logged-in Chrome for X +- `GET /export?q=` — JSON export +- `GET /export_netscape?q=` — Netscape export diff --git a/app.py b/app.py new file mode 100644 index 0000000..2a085cb --- /dev/null +++ b/app.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 +# COOKIE VAULT v2 — Netscape cookies.txt importer + one-click LOGIN launcher +# Host: Commando VM601, C:\cookievault\app.py, port 5066 +import os, re, json, sqlite3, time, datetime, threading, subprocess, tempfile +from flask import Flask, request, render_template_string, redirect, url_for, jsonify, Response + +APP_DIR = r"C:\cookievault" +DB_PATH = os.path.join(APP_DIR, "cookies.db") +WATCH_DIR = os.path.join(APP_DIR, "incoming") +SESSION_DIR = os.path.join(APP_DIR, "sessions") +os.makedirs(WATCH_DIR, exist_ok=True) +os.makedirs(SESSION_DIR, exist_ok=True) + +app = Flask(__name__) + +def db(): + c = sqlite3.connect(DB_PATH) + c.execute("""CREATE TABLE IF NOT EXISTS cookies( + id INTEGER PRIMARY KEY AUTOINCREMENT, + domain TEXT, flag TEXT, path TEXT, secure TEXT, + expiry INTEGER, name TEXT, value TEXT, + source_file TEXT, imported_at INTEGER, + UNIQUE(domain, path, name, source_file))""") + c.execute("CREATE INDEX IF NOT EXISTS ix_dom ON cookies(domain)") + return c + +def utc(ts): + try: return datetime.datetime.utcfromtimestamp(int(ts)).strftime("%Y-%m-%d") + except Exception: return "session" + +NS_RE = re.compile(r"^(#\S+)?\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$") + +def parse_netscape(text): + out = [] + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith("# ") or line == "#HttpOnly_": + continue + if line.startswith("#HttpOnly_"): + line = line[len("#HttpOnly_"):] + elif line.startswith("#"): + continue + parts = line.split("\t") if "\t" in line else line.split() + if len(parts) < 7: + continue + domain, flag, path, secure, expiry, name, value = parts[:7] + if len(parts) > 7: + value = "\t".join(parts[6:]) + out.append((domain, flag, path, secure, expiry, name, value)) + return out + +def import_file(path): + fname = os.path.basename(path) + with open(path, "r", encoding="utf-8", errors="replace") as f: + text = f.read() + cookies = parse_netscape(text) + c = db() + before = c.execute("SELECT COUNT(*) FROM cookies").fetchone()[0] + for ck in cookies: + c.execute("INSERT OR IGNORE INTO cookies(domain,flag,path,secure,expiry,name,value,source_file,imported_at) VALUES(?,?,?,?,?,?,?,?,?)", + (*ck, fname, int(time.time()))) + c.commit() + after = c.execute("SELECT COUNT(*) FROM cookies").fetchone()[0] + total = after + c.close() + return len(cookies), after - before, total + +PAGE = r"""COOKIE VAULT +

🍪 COOKIE VAULT

{{stats[0]}} cookies{{stats[1]}} domains{{stats[2]}} files
+
+
+

Import Netscape cookies.txt

+
+
Drop cookies.txt files here (as many as you want)  or  + +

Or paste raw cookie text:

+ +
+

also watches C:\cookievault\incoming — dump files there and hit rescan

+
+
+ {% if msg %}

{{msg|safe}}

{% endif %} +
+
+

Domains — one-click login

+
+ ALL + EXPORT JSON + EXPORT NETSCAPE
+ + {% for d in domains %} + + + + {% endfor %}
domaincookiesfresh untillogin
{{d[0]}}{{d[1]}}{{'has session cookies' if d[2]==0 else utc(d[2])}}LOGIN →
+

{{domains|length}} domains shown

+
+
+""" + +@app.route("/", methods=["GET"]) +def index(): + q = request.args.get("q", "").strip() + c = db() + if q: + like = f"%{q}%" + domains = c.execute("""SELECT domain, COUNT(*), MAX(CAST(expiry AS INTEGER)) FROM cookies + WHERE domain LIKE ? GROUP BY domain ORDER BY domain LIMIT 500""", (like,)).fetchall() + else: + domains = c.execute("""SELECT domain, COUNT(*), MAX(CAST(expiry AS INTEGER)) FROM cookies + GROUP BY domain ORDER BY domain LIMIT 500""").fetchall() + stats = (c.execute("SELECT COUNT(*) FROM cookies").fetchone()[0], + c.execute("SELECT COUNT(DISTINCT domain) FROM cookies").fetchone()[0], + c.execute("SELECT COUNT(DISTINCT source_file) FROM cookies").fetchone()[0]) + c.close() + return render_template_string(PAGE, domains=domains, stats=stats, q=q, msg=request.args.get("msg"), utc=utc) + +@app.route("/import", methods=["POST"]) +def do_import(): + msg, errs = [], [] + files = request.files.getlist("files") + for f in files: + try: + fname = os.path.basename(f.filename or "").strip() + # sanitize: keep alnum, dash, underscore, dot + fname = re.sub(r"[^A-Za-z0-9._\- ]", "_", fname) + if not fname or fname == ".": + # no filename — generate one from content hash + data = f.read() + if not data.strip(): + continue + fname = "unnamed_%d.txt" % int(time.time() * 1000 % 1000000000) + path = os.path.join(WATCH_DIR, fname) + with open(path, "wb") as fh: fh.write(data) + else: + path = os.path.join(WATCH_DIR, fname) + f.save(path) + n, new, total = import_file(path) + msg.append(f"{fname}: {n} parsed, {new} new") + except Exception as e: + errs.append(f"{getattr(f, 'filename', '?')}: {e}") + paste = request.form.get("paste", "").strip() + if paste: + try: + tmp = os.path.join(WATCH_DIR, "_pasted_%d.txt" % int(time.time())) + with open(tmp, "w", encoding="utf-8") as fh: fh.write(paste) + n, new, total = import_file(tmp) + msg.append(f"pasted: {n} parsed, {new} new") + except Exception as e: + errs.append(f"paste: {e}") + if errs: + msg += ["%s" % e for e in errs] + if not msg: + msg = ["nothing imported — pick a cookies.txt file or paste cookie text first"] + return redirect(url_for("index", msg=" · ".join(msg))) + +@app.route("/scan", methods=["POST"]) +def scan(): + msg, errs = [], [] + for fn in os.listdir(WATCH_DIR): + if fn.lower().endswith((".txt", ".json")): + try: + n, new, total = import_file(os.path.join(WATCH_DIR, fn)) + msg.append(f"{fn}: {new} new") + except Exception as e: + errs.append(f"{fn}: {e}") + msg += ["%s" % e for e in errs] + return redirect(url_for("index", msg=" · ".join(msg) or "nothing new in incoming")) + +def _launch_login(domain, cookies): + """Runs in background thread: selenium Chrome with cookies injected.""" + from selenium import webdriver + from selenium.webdriver.chrome.options import Options + opts = Options() + profile = os.path.join(SESSION_DIR, re.sub(r'[^a-zA-Z0-9]', '_', domain)) + os.makedirs(profile, exist_ok=True) + opts.add_argument(r"--user-data-dir=" + profile) + opts.add_argument("--no-first-run") + opts.add_argument("--no-default-browser-check") + opts.add_argument("--start-maximized") + driver = webdriver.Chrome(options=opts) # selenium-manager auto-fetches driver + try: + # land on the site's origin first so cookies can be set for it + host = domain.lstrip(".") + driver.get(f"https://{host}/favicon.ico") + except Exception: + try: driver.get(f"https://{host}/") + except Exception: pass + time.sleep(1) + for ck in cookies: + c = {"name": ck[1], "value": ck[2]} + dom = ck[0] + if not dom.startswith("."): + c["domain"] = dom + else: + c["domain"] = dom + c["path"] = ck[3] or "/" + try: + exp = int(ck[4]) + if exp > 0: + c["expiry"] = exp + except Exception: + pass + c["secure"] = str(ck[5]).upper() == "TRUE" + try: + driver.add_cookie(c) + except Exception: + try: + c2 = dict(c); c2.pop("expiry", None) + driver.add_cookie(c2) + except Exception: + pass + try: + driver.get(f"https://{host}/") + except Exception: + pass + # keep the process ref alive — selenium closes browser if driver is GC'd + globals().setdefault("_drivers", []).append(driver) + +@app.route("/open") +def open_login(): + domain = request.args.get("domain", "").strip() + if not domain: + return redirect(url_for("index")) + like = f"%{domain.strip('.')}%" + c = db() + rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies + WHERE domain LIKE ? OR domain LIKE ?""", + (domain.strip(".") + "%", "%" + domain.strip("."))).fetchall() + c.close() + if not rows: + return redirect(url_for("index", msg=f"no cookies stored for {domain}")) + threading.Thread(target=_launch_login, args=(domain, rows), daemon=True).start() + time.sleep(0.5) + return redirect(url_for("index", msg=f"launching Chrome for {domain} with {len(rows)} cookies...")) + +@app.route("/export") +def export(): + q = request.args.get("q", "").strip() + c = db() + if q: + like = f"%{q}%" + rows = c.execute("SELECT domain,name,value,path,expiry,secure FROM cookies WHERE domain LIKE ? OR name LIKE ?", (like, like)).fetchall() + else: + rows = c.execute("SELECT domain,name,value,path,expiry,secure FROM cookies").fetchall() + c.close() + jar = [{"domain": r[0], "name": r[1], "value": r[2], "path": r[3], + "expirationDate": int(r[4]) if r[4] and int(r[4]) > 0 else None, + "secure": r[5].upper() == "TRUE", "httpOnly": False} for r in rows] + return Response(json.dumps(jar, indent=2), mimetype="application/json", + headers={"Content-Disposition": "attachment; filename=cookies.json"}) + +@app.route("/export_netscape") +def export_ns(): + q = request.args.get("q", "").strip() + c = db() + if q: + like = f"%{q}%" + rows = c.execute("SELECT domain,flag,path,secure,expiry,name,value FROM cookies WHERE domain LIKE ? OR name LIKE ?", (like, like)).fetchall() + else: + rows = c.execute("SELECT domain,flag,path,secure,expiry,name,value FROM cookies").fetchall() + c.close() + lines = ["# Netscape HTTP Cookie File", "# Exported by COOKIE VAULT"] + lines += ["\t".join(str(x) for x in r) for r in rows] + return Response("\n".join(lines) + "\n", mimetype="text/plain", + headers={"Content-Disposition": "attachment; filename=cookies.txt"}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5066, debug=False) diff --git a/start_cookievault.bat b/start_cookievault.bat new file mode 100644 index 0000000..14e49c8 --- /dev/null +++ b/start_cookievault.bat @@ -0,0 +1,3 @@ +@echo off +cd /d C:\cookievault +python app.py >> C:\cookievault\app.log 2>&1