Automates Ivanti Secure Access Client VPN connection with TOTP injection on Windows
Project description
IvantAuto
Automates connection to Ivanti Secure Access Client (formerly Pulse Secure) on Windows, including automatic TOTP code injection. Designed for maintaining persistent VPN access to lab environments (e.g. Ollama models) without manual reconnection every hour.
How It Works
- Launches
pulselauncher.exewith your VPN credentials via CLI - Detects the TOTP dialog using
pywinauto(UIA backend) or a process-poll fallback - Injects the current 6-digit TOTP code automatically
- In daemon mode, monitors connectivity and reconnects automatically when the VPN drops
Requirements
- Windows 10/11 (or Windows Server)
- Python 3.10+
- Ivanti Secure Access Client installed (provides
pulselauncher.exe) - Your TOTP secret (see Extracting Your TOTP Secret below)
Installation
Option A — pip + git (no clone needed)
pip install git+https://github.com/chumicat/IvantAuto.git
This installs the ivantauto CLI command directly. You still need to create a config.ini (see Quick Start below).
Option B — clone and install (editable)
git clone https://github.com/chumicat/IvantAuto.git
cd IvantAuto
python -m venv .venv
.venv\Scripts\pip install -e .
Quick Start
Step 1 — Copy and fill in config
copy config.ini.template config.ini
Edit config.ini:
[host]
url = vpn.yourcompany.com ← copy exactly what the Ivanti client UI shows (no http/https unless the UI shows it)
test_domain = internal.yourcompany.com ← optional: internal host to ping for status checks
[auth]
username = john ← your VPN username
realm = Users ← your VPN realm (see "Finding Your Realm" below)
[start]
pulselauncher_path = ← leave blank to auto-detect
[options]
injection_strategy = window ← "window" (default) or "loop" (fallback)
otp_dialog_timeout = 60
reconnect_interval_min = 55 ← used by interval mode and force_reconnect safety net
[daemon]
daemon_mode = heartbeat ← "heartbeat" (reactive) or "interval" (fixed timer)
heartbeat_interval_sec = 5 ← seconds between pings (heartbeat mode)
heartbeat_fail_threshold = 3 ← consecutive failures before reconnect
force_reconnect = false ← force periodic reconnect even if VPN is healthy
Step 2 — Store credentials securely
.venv\Scripts\python -m ivantauto setup
You will be prompted for:
| Prompt | What to enter |
|---|---|
VPN password: |
Your VPN login password |
TOTP secret: |
The secret value from your otpauth:// URI (see below) |
Credentials are encrypted and stored in Windows Credential Manager (DPAPI-backed).
They are never written to config.ini or any file on disk.
Step 3 — Test a one-shot connection
.venv\Scripts\python -m ivantauto connect
To disconnect any existing session before connecting (recommended if a stale VPN or UI may be open):
.venv\Scripts\python -m ivantauto connect --clean-start
Step 4 — Run the daemon
.venv\Scripts\python -m ivantauto daemon
The daemon has two modes:
Heartbeat mode (default)
Pings test_domain every 5 seconds. If 3 consecutive pings fail, it triggers a full reconnect (disconnect + clean UI + reconnect). This is reactive — it only reconnects when the VPN actually drops.
.venv\Scripts\python -m ivantauto daemon --mode heartbeat
If a reconnect fails, the daemon backs off exponentially (10s → 20s → ... up to 5 min between checks) and keeps retrying indefinitely. On successful reconnect, the poll interval resets to normal.
Requires
test_domaininconfig.ini. If not set, the daemon falls back to interval mode automatically.
Interval mode
The original fixed-timer approach: reconnect every N minutes regardless of connectivity status. Useful if you don't have a test_domain to ping.
.venv\Scripts\python -m ivantauto daemon --mode interval
Daemon flags
| Flag | Effect |
|---|---|
--clean-start |
Disconnect before the first connect cycle |
--force-reconnect |
Force a periodic disconnect+reconnect even if VPN appears healthy (heartbeat mode uses reconnect_interval_min as the interval; interval mode disconnects before every cycle) |
--mode {heartbeat,interval} |
Override daemon mode from config.ini |
Permanent config equivalents:
[options]
clean_start = true
[daemon]
daemon_mode = heartbeat
force_reconnect = true
; Heartbeat settings (only used in heartbeat mode)
heartbeat_interval_sec = 5
heartbeat_fail_threshold = 3
What happens during a reconnect?
Every reconnect (whether triggered by heartbeat failure, interval timer, or retry) performs:
- Full disconnect — stops
PulseSecureService(drops the VPN tunnel) - Kill all Pulse* processes — removes
Pulse.exe,PulseSetupClient.exe, etc. from the tray - Restart service — starts
PulseSecureServicefresh - Launch
pulselauncher.exe— with credentials - Fill dialogs — handles credential dialog (username/password) then TOTP dialog automatically
- Verify — pings
test_domainto confirm the VPN is up
If the TOTP injection succeeds but the VPN doesn't come up, it retries up to connect_max_retries times (default: 2), with a full disconnect+cleanup between each retry.
Finding Your Realm
The realm is the authentication domain name configured on the Ivanti gateway. It may or may not appear as a dropdown on the login page.
If there is a dropdown on the login page
The selected value is your realm — copy it exactly into config.ini.
If there is no dropdown (hidden realm)
Use browser DevTools to capture it from the login request:
- Open your VPN portal URL in a browser
- Press F12 → go to the Network tab
- Enable Preserve log (checkbox near the top) so requests don't clear on redirect
- Submit the login form normally
- In the Network tab, find the request named
login.cgiwith status 200 (ignore the one with status 302 — that is the redirect after login) - Click it → open the Payload tab (Chrome) / Request tab (Firefox)
- Under Form Data, find the field named
realm
The value is your realm — spaces are allowed, copy it exactly.
Example form data you might see:
username=john&password=...&realm=Ldaps user&SubmitButton=Sign+In
→ realm is Ldaps user
Extracting Your TOTP Secret
Your authenticator app exports TOTP accounts in this format:
otpauth://totp/<user>?secret=<SECRET>&issuer=<issuer>
Example:
otpauth://totp/john%40company.com?secret=JBSWY3DPEHPK3PXP&issuer=CompanyVPN
What to extract: the value after secret= up to the next & (or end of string).
In the example above: JBSWY3DPEHPK3PXP
This is the base32 TOTP secret — paste it at the TOTP secret: prompt during ivantauto setup.
The
<user>andissuer=parts are labels only — IvantAuto does not need them.
How to get the otpauth:// URI from your app
| App | Method |
|---|---|
| Google Authenticator | Account → three-dot menu → Export accounts → scan QR with a decoder like zxing.org/w/decode.jspx |
| Authy | Enable backups, use Authy desktop, or extract via this guide |
| 1Password / Bitwarden | View the item — the TOTP secret is shown directly in the field |
| Microsoft Authenticator | Export to backup, or ask your IT team for the provisioning QR code |
| Company-issued QR code | Scan with a QR reader — the decoded text is the full otpauth:// URI |
If you only have a QR code image, decode it at zxing.org/w/decode.jspx to get the URI, then copy the secret= value.
All Commands
.venv\Scripts\python -m ivantauto [command]
| Command | Description |
|---|---|
setup |
Store VPN password and TOTP secret in Windows Credential Manager |
connect |
One-shot: connect once and exit. --clean-start to disconnect first |
daemon |
Persistent daemon. --mode heartbeat (default, reactive) or --mode interval (fixed timer). --clean-start / --force-reconnect |
status |
Ping test_domain to check if VPN is up |
clear-creds |
Remove all stored credentials |
debug-windows |
List all visible window titles every 2s (use this to find the OTP dialog name) |
Global flags: -v (verbose/debug logging), -c config.ini (custom config path)
Troubleshooting
OTP dialog not detected
Run debug-windows while the Ivanti client is showing its TOTP prompt:
.venv\Scripts\python -m ivantauto debug-windows
Note the exact window title, then add it to config.ini:
[options]
otp_window_titles = My Custom Dialog Title
Multiple titles: comma-separated.
Falls back to loop strategy
If window strategy can't find controls (Ivanti uses a non-standard UI renderer), switch to:
injection_strategy = loop
The loop strategy polls for pulselauncher.exe and types the TOTP into whatever has keyboard focus.
Permission denied
Run your terminal as Administrator, or check that pulselauncher.exe is reachable at the detected path.
pulselauncher.exe not found
Set the path explicitly in config.ini:
[start]
pulselauncher_path = C:\path\to\pulselauncher.exe
Security Notes
- Password exposure:
pulselauncher.exereceives the password as a CLI argument, which is briefly visible in Task Manager. This is a limitation of the Ivanti CLI design, not IvantAuto. - TOTP secret: Stored in Windows Credential Manager (encrypted with your Windows login via DPAPI). Not written to any file.
- config.ini: Contains no secrets — only URL, username, and realm (non-sensitive).
Future: MCP / Service Integration
daemon.py exposes hook points for notifying other services before/after reconnect:
from ivantauto.daemon import on_before_reconnect, on_after_reconnect
on_before_reconnect(lambda: print("Pausing Ollama client..."))
on_after_reconnect(lambda: print("Resuming Ollama client..."))
Planned: integration with MCP tools (e.g. openclaw) so that 24/7 agents can pause gracefully during reconnect and resume automatically. Not yet implemented.
License
MIT License — see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ivantauto-0.1.0.tar.gz.
File metadata
- Download URL: ivantauto-0.1.0.tar.gz
- Upload date:
- Size: 22.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb1409d79b182c6917c3c5fd54e388fff29e1fb65ed57a7d3c2cb5317b5ec62d
|
|
| MD5 |
4392b96f66e165c2e9fde08dc410cfac
|
|
| BLAKE2b-256 |
a02d12f6bbd10edab3aa2d697cc5eb0b09e5352c43c1479b652923af2470b9e2
|
File details
Details for the file ivantauto-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ivantauto-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d7b544811930b34cf69e34c9835c043ea307fa3bc9e363f87acf99c0c74582e
|
|
| MD5 |
68fdcdaed2a0a916b7d3d83d73dd4240
|
|
| BLAKE2b-256 |
61907550828ef94e115b0493fce86d09c79d8039c5018059ef0e62a0ab217dfc
|