Notice // Official Self-Hosted Code Mirror
My GitHub profile (@shehzade) is temporarily suspended due to an automated false-positive on open-source Active Directory security tool releases and is currently under official reinstatement review by GitHub Support. This mirror provides verified source code, file trees, commit logs, and release binaries directly from local git repositories.

lanyard

AI-driven badge OSINT, credential harvesting, and calibrated CR80 card cloning for physical red teams.
git clone https://shehzade.io/repos/lanyard.git
Log | Files | Refs | README | LICENSE
↓ Download Source ZIP git clone https://shehzade.io/repos/lanyard.git

commit 290db81e74f9ee18b3b58421e076e56aefb27d37
parent 148e46e5d80b184c7820fc7a03faaeff536899f5
Author: Abdullah Ansari <shehzade@users.noreply.github.com>
Date:   Fri,  6 Nov 2026 20:10:36 +0100

feat: add dashboard and CR80 showcases to README, refine search queries, and improve session resilience

Diffstat:
MREADME.md | 13+++++++++++++
Aassets/cr80-kosta-briger.png | 0
Aassets/cr80-shi-choong.png | 0
Aassets/cr80-wendy-ngoh.png | 0
Aassets/dashboard-demo.png | 0
Mlanyard/browser/driver.py | 3++-
Mlanyard/cli.py | 14+++++++++-----
Mlanyard/sources/linkedin.py | 7+++++++
8 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md @@ -2,6 +2,10 @@ AI-driven badge OSINT, credential harvesting, and calibrated CR80 card cloning for physical red teams. +<p align="center"> + <img src="assets/dashboard-demo.png" alt="Lanyard Badge Intelligence Dossier" width="100%"> +</p> + ## Table of Contents - [Description](#description) @@ -199,6 +203,15 @@ Open `http://127.0.0.1:8088/report.html` in your browser. The gallery includes: - Pan and zoom controls for reading dense text, barcodes, and chip serials. - Filter controls for badge type (employee, contractor, visitor). +#### Calibrated CR80 Card Reconstruction (300 DPI) + +Lanyard detects badge boundary corners and applies a perspective homography matrix to output flat, print-ready PVC cards (85.6mm × 54.0mm): + +| Shi Choong (Intel) | Wendy Ngoh (Intel) | Kosta Briger (Intel) | +| :---: | :---: | :---: | +| <img src="assets/cr80-shi-choong.png" width="280" alt="Shi Choong CR80 Badge"> | <img src="assets/cr80-wendy-ngoh.png" width="280" alt="Wendy Ngoh CR80 Badge"> | <img src="assets/cr80-kosta-briger.png" width="280" alt="Kosta Briger CR80 Badge"> | +| *Extracted: Name, Photo, Corporate Branding* | *Extracted: Name, Photo, RFID Geometry* | *Extracted: Dual-Language Typography, Badge Reel* | + ### 4. Exporting Reports Regenerate HTML, CSV, and JSONL deliverables from an existing `lanyard.db`: diff --git a/assets/cr80-kosta-briger.png b/assets/cr80-kosta-briger.png Binary files differ. diff --git a/assets/cr80-shi-choong.png b/assets/cr80-shi-choong.png Binary files differ. diff --git a/assets/cr80-wendy-ngoh.png b/assets/cr80-wendy-ngoh.png Binary files differ. diff --git a/assets/dashboard-demo.png b/assets/dashboard-demo.png Binary files differ. diff --git a/lanyard/browser/driver.py b/lanyard/browser/driver.py @@ -32,7 +32,8 @@ def get_browser_context(headless: bool = True) -> Generator[BrowserContext, None finally: try: current_cookies = context.cookies() - if current_cookies: + has_auth = any(c.get("name") in ("li_at", "li_rm") for c in current_cookies) + if current_cookies and has_auth: save_cookies(current_cookies) except Exception: pass diff --git a/lanyard/cli.py b/lanyard/cli.py @@ -249,11 +249,15 @@ def hunt( queries = [f'"{company}" ({keyword.strip()})'] elif multi_query: queries = [ - f'"{company}" (badge OR "security pass" OR "access card" OR "ID badge")', - f'"{company}" ("first day" OR "day 1" OR "day one" OR "new role" OR "welcome to")', - f'"{company}" ("new intern" OR "internship" OR "joining" OR "first week")', - f'"{company}" ("last day" OR "leaving" OR "farewell" OR "final day")', - f'"{company}" (lanyard OR "employee badge" OR "work badge")', + f'{company} "ID badge"', + f'{company} "employee badge"', + f'{company} "first day" badge', + f'{company} "last day" badge', + f'"As I depart {company}"', + f'{company} "turned in my badge"', + f"{company} lanyard", + f"{company} intern badge", + f'{company} "got my badge"', ] else: search_kw = " OR ".join(f'"{k}"' for k in cfg.default_keywords[:3]) diff --git a/lanyard/sources/linkedin.py b/lanyard/sources/linkedin.py @@ -16,6 +16,9 @@ class LinkedInScraper: def is_authenticated(self, page: Page) -> bool: url = page.url.lower() + if "remember-me" in url or "ssr-login" in url: + time.sleep(4.0) + url = page.url.lower() return not ("login" in url or "checkpoint" in url or "authwall" in url) def warm_session(self, page: Page) -> bool: @@ -26,6 +29,8 @@ class LinkedInScraper: timeout=30000, ) time.sleep(2.5) + if "remember-me" in page.url.lower() or "ssr-login" in page.url.lower(): + time.sleep(4.0) return self.is_authenticated(page) except Exception: return False @@ -50,6 +55,8 @@ class LinkedInScraper: console.print(f"[cyan][*] Navigating to search feed: [bold]{query}[/bold][/cyan]") page.goto(search_url, wait_until="domcontentloaded", timeout=45000) time.sleep(3.5) + if "remember-me" in page.url.lower() or "ssr-login" in page.url.lower(): + time.sleep(4.0) if not self.is_authenticated(page): console.print("[dim yellow][*] Session warming up via /feed...[/dim yellow]")