Skip to main content
A QtRecon workspace is an SQLite database file that stores all your reconnaissance data, including discovered hosts, open ports, credentials, job outputs, and notes.

Database structure

QtRecon uses an in-memory SQLite database during runtime for performance. The database contains six primary tables:

hosts table

Stores discovered hosts and their metadata:
CREATE TABLE hosts(
  id integer primary key autoincrement not null,
  os text,
  ip UNIQUE,
  hostname,
  mac,
  highlight text,
  pwned integer not null default 0 check(pwned IN (0,1)),
  nmap,
  notes
)
Fields:
  • id: Unique host identifier
  • os: Operating system (linux, windows, ios, unknown)
  • ip: IP address (must be unique)
  • hostname: DNS hostname or custom label
  • mac: MAC address
  • highlight: Color highlighting (Qt color name like “yellow”, “red”)
  • pwned: Boolean flag indicating compromised status
  • nmap: Raw nmap output text
  • notes: HTML-formatted notes

hosts_ports table

Stores open ports discovered on each host:
CREATE TABLE hosts_ports(
  host_id integer,
  proto text,
  port,
  status text,
  description text
)
Fields:
  • host_id: Foreign key to hosts table
  • proto: Protocol (“tcp” or “udp”)
  • port: Port number
  • status: Port status (“open”, “closed”, “filtered”)
  • description: Service description from nmap

hosts_tabs table

Stores tool output and custom tabs for each host:
CREATE TABLE hosts_tabs(
  id integer primary key autoincrement not null,
  host_id integer,
  job_id integer,
  cmdline text,
  title text,
  text
)
Fields:
  • id: Unique tab identifier
  • host_id: Foreign key to hosts table
  • job_id: Associated job identifier
  • cmdline: Command that generated this output
  • title: Tab display name
  • text: Tab content (tool output)
Tabs are automatically created when you run attached jobs. The output is captured in real-time and stored in the database.

hosts_creds table

Stores discovered or imported credentials:
CREATE TABLE hosts_creds(
  id INTEGER primary key autoincrement not null,
  host_id integer,
  type TEXT DEFAULT 'password',
  domain TEXT DEFAULT '',
  username TEXT DEFAULT '',
  password TEXT DEFAULT ''
)
Fields:
  • id: Unique credential identifier
  • host_id: Foreign key to hosts table
  • type: Credential type (“password” or “hash”)
  • domain: Domain or realm
  • username: Username or account name
  • password: Password or hash value

logs table

Stores application logs and events:
CREATE TABLE logs(
  id integer primary key autoincrement not null,
  date,
  type,
  log
)
Fields:
  • id: Unique log entry identifier
  • date: Timestamp in ISO format
  • type: Log level (“RUNTIME”, “INFO”, “WARNING”, “CRITICAL”, “AUTORUN”)
  • log: Log message text
By default, RUNTIME logs are excluded when saving a workspace. You can configure this behavior in user preferences.

jobs table

Tracks job execution history:
CREATE TABLE jobs(
  id integer primary key autoincrement not null,
  host_id integer,
  type,
  timestamp,
  state,
  command
)
Fields:
  • id: Unique job identifier
  • host_id: Associated host (if applicable)
  • type: Job type (“scan”, “attached_program”)
  • timestamp: Job creation time
  • state: Current state (“Running”, “Success (0)”, “Crashed”)
  • command: Full command line executed
Jobs are not persisted when you save a workspace. Only the job outputs in hosts_tabs are saved.

Database initialization

The in-memory database is created at application startup:
core/database.py
@staticmethod
def init_DB():
    Database.database = sqlite3.connect(':memory:', check_same_thread=False)
    Database.database.row_factory = lambda C, R: {c[0]: R[i] for i, c in enumerate(C.description)}
    
    Database.database.execute("CREATE TABLE hosts(...)")
    Database.database.execute("CREATE TABLE hosts_ports(...)")
    Database.database.execute("CREATE TABLE hosts_tabs(...)")
    Database.database.execute("CREATE TABLE hosts_creds(...)")
    Database.database.execute("CREATE TABLE logs(...)")
    Database.database.execute("CREATE TABLE jobs(...)")
    
    Database.has_unsaved_data = False
    Database.current_savefile = ""
The row_factory configuration returns query results as dictionaries instead of tuples, making data access more convenient.

Saving workspaces

You can save your workspace to a file at any time:
core/database.py
@staticmethod
def export_DB(filename: str) -> Exception:
    Database.database.commit()
    
    try:
        dest = sqlite3.connect(filename)
        Database.database.backup(dest, pages=1, sleep=1)
        
        # Delete all jobs
        dest.execute("DELETE FROM jobs;")
        
        if Config.get()['user_prefs']['delete_logs_on_save']:
            dest.execute("DELETE FROM logs;")
            dest.execute("UPDATE SQLITE_SEQUENCE SET SEQ=0 WHERE NAME='logs';")
        else:
            dest.execute("DELETE FROM logs WHERE type == 'RUNTIME';")
        
        dest.commit()
    except sqlite3.OperationalError as e:
        return e
    
    Database.current_savefile = filename
Save behavior:
  • Creates a copy of the in-memory database
  • Removes active jobs (not needed in saved state)
  • Optionally removes logs based on preferences
  • Always removes RUNTIME logs

Autosave

QtRecon can automatically save your workspace at regular intervals:
core/controller.py
def autosave(self):
    if self.ui.ui.actionAutosave_database_every_5_mins.isChecked() and Database.current_savefile:
        self.save_db()
The autosave interval is configurable in user_prefs.autosave_interval (default: 300000ms = 5 minutes).
Enable autosave from File → Autosave database every 5 mins to prevent data loss.

Loading workspaces

You can load a previously saved workspace:
core/database.py
@staticmethod
def import_DB(filename: str):
    if not os.path.isfile(filename) or not os.access(filename, os.R_OK):
        return f'database file "{filename}" not found.'
    
    try:
        source = sqlite3.connect(filename, check_same_thread=False)
    except sqlite3.OperationalError as e:
        return e
    
    Database.init_DB()
    source.backup(Database.database)
    
    # Compatibility migrations...
    Database.database.commit()
    Database.current_savefile = filename

Backward compatibility

QtRecon includes migration code for older database formats:
core/database.py
# Compatibility code: if table hosts_creds does not exist (QtRecon < 1.2), create it
if not Database.request("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'hosts_creds'").fetchall():
    Database.database.execute("CREATE TABLE hosts_creds(...)")

# Compatibility code: if table hosts has not UNIQUE attribute on ip (QtRecon < 1.6)
if 'ip UNIQUE' not in Database.request("SELECT sql FROM sqlite_schema WHERE name='hosts';").fetchone()['sql']:
    Database.database.execute("CREATE UNIQUE INDEX host_ip ON hosts(ip);")

# Compatibility code: if table hosts_tabs has no cmdline field (QtRecon < 1.7)
if Database.request("SELECT COUNT(*) as count FROM pragma_table_info('hosts_tabs') WHERE name='cmdline'").fetchone()['count'] == 0:
    # Migrate table structure...
This ensures workspaces created with older versions remain compatible.

Unsaved changes tracking

QtRecon tracks whether you have unsaved changes:
core/database.py
@staticmethod
def request(request: str, args: tuple = ()) -> sqlite3.Cursor:
    cursor = Database.database.cursor()
    res = cursor.execute(request, args)
    
    if args.count('RUNTIME') != 1 and any(request.lower().startswith(command.lower()) 
                                          for command in ['update', 'insert', 'delete']):
        Database.has_unsaved_data = True
    
    return res
The has_unsaved_data flag is set whenever data-modifying queries are executed (except RUNTIME logs).

Querying the database

Models interact with the database using the static Database.request() method:
# Fetch all hosts
hosts = Database.request("SELECT id, os, ip, hostname FROM hosts").fetchall()

# Fetch ports for a specific host
ports = Database.request('SELECT * FROM hosts_ports WHERE host_id = ?', (host_id,)).fetchall()

# Update host notes
Database.request("UPDATE hosts SET notes = ? WHERE id = ?", (notes, host_id))
Query results:
  • fetchone(): Returns a single dict or None
  • fetchall(): Returns a list of dicts
  • .lastrowid: Gets the ID of the last inserted row
All database queries should use parameterized statements with ? placeholders to prevent SQL injection.

File format

QtRecon workspace files are standard SQLite 3 database files:
# Inspect a workspace file
sqlite3 my-project.qtrecon

# List tables
sqlite> .tables
hosts        hosts_creds  hosts_tabs   logs
hosts_ports  jobs

# Query hosts
sqlite> SELECT ip, hostname, os FROM hosts;
You can use any SQLite client to inspect or modify workspace files directly.
Direct database modifications may break data integrity. Always back up your workspace before manual editing.