16 Commits

Author SHA1 Message Date
Hadi 25fb5a4bf0 Fix TOC links
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 21:03:40 +02:00
Hadi d257a0f26e update repos
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:52:09 +02:00
Hadi 3dfbdcf970 New separators
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:51:46 +02:00
Hadi 930c3bf3bb Clean homepage
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:40:22 +02:00
Hadi a055640fa8 Edit hero & add Infosec Notes section
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:21:17 +02:00
Hadi 9c0bbc4b77 edit sidebars
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:16:01 +02:00
Hadi 7968c662f6 Edit sidebar
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 17:03:05 +02:00
Hadi 99890dd1ef Add crt.sh
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 16:03:15 +02:00
Hadi db42928299 Add Google cache & VirusTotal
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 16:03:12 +02:00
Hadi 73b668b204 Change link-cards height
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 15:19:20 +02:00
Hadi c314445219 Init RDP
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:17:54 +02:00
Hadi b4b755b608 Init ssh
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:15:59 +02:00
Hadi 3e60ae5a35 Change wordlists paths
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:11:38 +02:00
Hadi 4f64ccf706 Init telnet
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:11:03 +02:00
Hadi d6d410a2fa Add notes & card styling
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:03:48 +02:00
Hadi a74f6b91d4 remove dead code and unify patterns
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-29 23:03:10 +02:00
35 changed files with 896 additions and 599 deletions
-3
View File
@@ -14,7 +14,6 @@
"@types/bun": "^1.3.13",
"astro": "6.1.9",
"daisyui": "^5.5.19",
"lucide-astro": "^0.556.0",
"node-html-parser": "^7.1.0",
"svelte": "^5.55.5",
"tailwindcss": "^4.2.4",
@@ -624,8 +623,6 @@
"lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
"lucide-astro": ["lucide-astro@0.556.0", "", { "peerDependencies": { "astro": ">=2.7.1" } }, "sha512-ugMjPb45AMfkLCaduNSbyy5NQEKvB1TxVVMmUS4S6L807PMESnX0Qp+DIKHjbyjJmPXOyLRbrzvR3YikTK7brg=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="],
-4
View File
@@ -1273,10 +1273,6 @@
url = "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz";
hash = "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==";
};
"lucide-astro@0.556.0" = fetchurl {
url = "https://registry.npmjs.org/lucide-astro/-/lucide-astro-0.556.0.tgz";
hash = "sha512-ugMjPb45AMfkLCaduNSbyy5NQEKvB1TxVVMmUS4S6L807PMESnX0Qp+DIKHjbyjJmPXOyLRbrzvR3YikTK7brg==";
};
"magic-string@0.30.21" = fetchurl {
url = "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz";
hash = "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==";
+1 -2
View File
@@ -21,8 +21,7 @@
"@types/bun": "^1.3.13",
"astro": "6.1.9",
"daisyui": "^5.5.19",
"lucide-astro": "^0.556.0",
"node-html-parser": "^7.1.0",
"node-html-parser": "^7.1.0",
"svelte": "^5.55.5",
"tailwindcss": "^4.2.4"
},
+1 -1
View File
@@ -1,5 +1,5 @@
---
import { ArrowUp } from "lucide-astro";
import { ArrowUp } from "@lucide/astro";
---
<button
+10 -5
View File
@@ -18,14 +18,19 @@ const latestPosts = sortedPosts.slice(0, 3);
<div class="max-w-6xl mx-auto">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2>
<p class="text-lg text-base-content/70">
Thoughts, insights, and tutorials on cybersecurity, OSINT, and
technology.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{latestPosts.map((post) => <BlogCard post={post} />)}
{
latestPosts.map((post) => (
<BlogCard
displayBanner={false}
displayTags={false}
displayDate={false}
post={post}
/>
))
}
</div>
<div class="text-center mt-12">
+13 -14
View File
@@ -2,26 +2,23 @@
import type { CollectionEntry } from "astro:content";
import TagBadge from "./TagBadge.astro";
import { Image } from "astro:assets";
import { formatDate } from "../utils/notes";
interface Props {
displayBanner: boolean;
displayDate: boolean;
displayTags: boolean;
post: CollectionEntry<"blog">;
}
const { post } = Astro.props;
function formatDate(date: Date) {
const options: Intl.DateTimeFormatOptions = {
month: "long",
day: "numeric",
year: "numeric",
};
return date.toLocaleDateString("en-US", options);
}
---
<article
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
>
{ Astro.props.displayBanner && (
<figure class="aspect-video">
<Image
src={post.data.image}
@@ -31,25 +28,27 @@ function formatDate(date: Date) {
height={400}
/>
</figure>
)}
<div class="card-body">
{ Astro.props.displayDate && (
<time class="text-sm text-base-content/60">
{formatDate(post.data.publishDate)}
</time>
)}
<h2 class="card-title hover:text-primary transition-colors">
<a href={`/blog/${post.id}`}>{post.data.title}</a>
</h2>
<p class="text-base-content/80">{post.data.description}</p>
{
post.data.tags && post.data.tags.length > 0 && (
{ Astro.props.displayTags && post.data.tags && post.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mt-2">
{post.data.tags.map((tag) => (
<TagBadge tag={tag} />
))}
</div>
)
}
)}
<div class="card-actions justify-end mt-4">
<a href={`/blog/${post.id}`} class="btn btn-primary btn-sm">
<a href={`/blog/${post.id}`} class="btn btn-soft btn-primary btn-sm">
Read More
</a>
</div>
+5 -2
View File
@@ -2,6 +2,7 @@
import { ExternalLink, ChevronDown } from "@lucide/astro";
interface Props {
displayBanner: boolean;
repo: {
name: string;
description: string;
@@ -34,6 +35,7 @@ const hasMultiplePlatforms = platforms.length > 1;
<article
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
>
{ Astro.props.displayBanner && repo.banner_url && (
<figure class="aspect-video bg-base-200 overflow-hidden">
<img
src={repo.banner_url}
@@ -42,6 +44,7 @@ const hasMultiplePlatforms = platforms.length > 1;
onerror="this.parentElement.style.display='none'"
/>
</figure>
)}
<div class="card-body">
<h2 class="card-title hover:text-primary transition-colors">
@@ -83,7 +86,7 @@ const hasMultiplePlatforms = platforms.length > 1;
<div
tabindex="0"
role="button"
class="btn btn-primary btn-sm gap-1"
class="btn btn-soft btn-primary btn-sm gap-1"
>
<ExternalLink class="size-4" />
View Source
@@ -107,7 +110,7 @@ const hasMultiplePlatforms = platforms.length > 1;
href={repo.html_url}
target="_blank"
rel="noopener noreferrer"
class="btn btn-primary btn-sm gap-1"
class="btn btn-soft btn-primary btn-sm gap-1"
>
<ExternalLink class="size-4" />
View on Gitea
+9 -42
View File
@@ -1,26 +1,14 @@
---
import { ArrowRight, FolderCode, Key, Rss } from "@lucide/astro";
import { FolderCode, Key, Rss } from "@lucide/astro";
import { Image } from "astro:assets";
import type { SocialLinks } from "../config";
interface Props {
name: string;
title: string;
description: string;
avatar: any;
location?: string;
socialLinks?: {
github?: string;
gitlab?: string;
gitea?: string;
linkedin?: string;
twitter?: string;
bluesky?: string;
instagram?: string;
youTube?: string;
medium?: string;
kofi?: string;
codetips?: string;
};
socialLinks?: SocialLinks;
gpgKey?: string;
rssFeed?: string;
}
@@ -30,18 +18,17 @@ const {
title,
description,
avatar,
location,
socialLinks,
gpgKey,
rssFeed,
} = Astro.props;
---
<section class="hero min-h-[65vh]">
<div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-8">
<section class="hero py-20">
<div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-10">
<div class="avatar">
<div
class="w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2"
class="w-32 md:w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2"
>
<Image src={avatar} alt={name} />
</div>
@@ -51,8 +38,7 @@ const {
Hi, I'm {name}
</h1>
<p class="text-xl text-base-content/80 mb-2">{title}</p>
{location && <p class="text-base-content/60 mb-4">{location}</p>}
<p class="text-lg leading-relaxed mb-6">
<p class="text-lg max-w-lg leading-relaxed mb-6">
{description}
</p>
{
@@ -205,10 +191,10 @@ const {
</a>
</div>
)}
{socialLinks.youTube && (
{socialLinks.youtube && (
<div class="tooltip" data-tip="Youtube">
<a
href={socialLinks.youTube}
href={socialLinks.youtube}
class="btn btn-circle btn-ghost"
aria-label="YouTube"
target="_blank"
@@ -346,25 +332,6 @@ const {
</div>
)
}
<div class="mt-12 flex flex-wrap gap-5">
<a href="/blog" class="btn btn-ghost gap-2">
Blog Posts
<ArrowRight class="size-4" />
</a>
<a href="/projects" class="btn btn-ghost gap-2">
Projects
<ArrowRight class="size-4" />
</a>
<a href="/notes" class="btn btn-ghost gap-2">
Infosec Notes
<ArrowRight class="size-4" />
</a>
<a href="/#contact" class="btn btn-ghost gap-2">
Contact Me
<ArrowRight class="size-4" />
</a>
</div>
</div>
</div>
</section>
-2
View File
@@ -110,8 +110,6 @@ function isActive(href: string) {
if (!btn || !menu) return;
const lines = btn.querySelectorAll<HTMLElement>(".hamburger-line");
let open = false;
open = false;
menu.style.display = "none";
lines[0].style.transform = "";
lines[1].style.opacity = "";
+1
View File
@@ -269,6 +269,7 @@
});
</script>
<!-- svelte-ignore a11y_no_interactive_element_to_noninteractive_role -->
<canvas
bind:this={canvas}
height="190"
+28 -2
View File
@@ -10,9 +10,10 @@ interface Props {
graphEdges: { from: string; to: string }[];
forwardLinks: CollectionEntry<"notes">[];
backlinks: CollectionEntry<"notes">[];
externalLinks: { url: string; label: string }[];
}
const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
const { entry, graphNodes, graphEdges, forwardLinks, backlinks, externalLinks } = Astro.props;
---
<aside
@@ -26,7 +27,7 @@ const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
>
graph
</p>
<NoteGraph client:visible nodes={graphNodes} edges={graphEdges} />
<NoteGraph client:load nodes={graphNodes} edges={graphEdges} />
{
graphNodes.length < 2 && (
<p class="font-mono text-[9px] text-base-content/20 text-center py-2">
@@ -90,6 +91,31 @@ const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
)
}
{
externalLinks.length > 0 && (
<div class="p-3 border-t border-base-300/40">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">
external
</p>
<ul class="space-y-1">
{externalLinks.map(({ url, label }) => (
<li>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate"
title={url}
>
↗ {label}
</a>
</li>
))}
</ul>
</div>
)
}
<div class="px-4 pt-4 pb-1 border-t border-base-300/40 mt-auto">
<time
datetime={entry.data.publishDate.toISOString()}
@@ -65,25 +65,7 @@
}
</script>
<aside
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-[calc(100vh-3rem)]"
style="background: oklch(4% 0 0);"
>
<!-- Mobile close bar -->
<div class="lg:hidden flex items-center justify-between px-3 py-2 border-b border-base-300/40 shrink-0">
<span class="font-mono text-[10px] text-base-content/30 uppercase tracking-widest">nav</span>
<label
for="nav-drawer"
class="cursor-pointer text-base-content/30 hover:text-base-content/70 transition-colors p-1"
aria-label="close sidebar"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</label>
</div>
<!-- Search -->
<div class="flex flex-col flex-1 min-h-0">
<div class="px-3 py-3 border-b border-base-300/40 shrink-0">
<div
class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-base-200/50 border border-base-300/40 focus-within:border-base-300/70 transition-colors"
@@ -98,7 +80,6 @@
</div>
</div>
<!-- Nav -->
<nav class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-px">
{#each categories as cat}
{@const catNotes = notes.filter(
@@ -107,7 +88,6 @@
{#if catNotes.length > 0 || !search}
{@const isFolder = notes.some((n) => n.id.includes("/") && getCategory(n) === cat)}
<div>
<!-- Category header -->
<div class="flex items-center w-full">
<button
onclick={() => toggle(cat)}
@@ -142,7 +122,6 @@
{/if}
</div>
<!-- Notes list -->
{#if openCategories.includes(cat)}
<ul
class="ml-4 mt-0.5 pb-1 space-y-px"
@@ -170,4 +149,4 @@
{/if}
{/each}
</nav>
</aside>
</div>
+26
View File
@@ -0,0 +1,26 @@
---
import NoteNavContent from "./NoteNavContent.svelte";
import type { CollectionEntry } from "astro:content";
interface Props {
notes: CollectionEntry<"notes">[];
currentEntry?: CollectionEntry<"notes">;
currentCategory?: string;
categories: string[];
}
const { notes, currentEntry, currentCategory, categories } = Astro.props;
---
<aside
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-full"
style="background: oklch(4% 0 0);"
>
<NoteNavContent
client:load
notes={notes}
currentEntry={currentEntry}
currentCategory={currentCategory}
categories={categories}
/>
</aside>
+4 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount } from "svelte";
import { onMount, untrack } from "svelte";
interface Props {
vars: string[];
@@ -8,7 +8,7 @@
const { vars }: Props = $props();
let values = $state<Record<string, string>>(
Object.fromEntries(vars.map((v) => [v, ""])),
untrack(() => Object.fromEntries(vars.map((v) => [v, ""]))),
);
let open = $state(false);
let applied = $state(false);
@@ -71,12 +71,14 @@
{#each vars as v}
<div class="flex items-center gap-3">
<label
for={`var-${v}`}
class="font-mono text-xs text-primary/70 w-36 shrink-0 truncate"
title={`$${v}`}
>
${v}
</label>
<input
id={`var-${v}`}
type="text"
bind:value={values[v]}
placeholder={`$${v}`}
+25
View File
@@ -0,0 +1,25 @@
---
import { ArrowRight } from "@lucide/astro";
---
<section id="notes" class="py-10 px-4">
<div class="max-w-6xl mx-auto text-center">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Infosec Notes</h2>
</div>
<div class="mt-8">
<p class="text-base text-base-content/70 mb-6">
Cheatsheets and references on tools and techniques I use for CTFs and
pentesting.
</p>
<div class="text-center mt-12">
<a href="/notes" class="btn btn-ghost gap-2">
Browse Notes
<ArrowRight class="size-4" />
</a>
</div>
</div>
</div>
</section>
+6 -6
View File
@@ -10,14 +10,14 @@ const latestRepos = repos.slice(0, 3);
<div class="max-w-6xl mx-auto">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Check out my latest work</h2>
<p class="text-lg text-base-content/70">
I enjoy the challenge of reimagining existing programs & scripts in my
own unique way.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{latestRepos.map((repo) => <GiteaProjectCard repo={repo} />)}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{
latestRepos.map((repo) => (
<GiteaProjectCard displayBanner={false} repo={repo} />
))
}
</div>
<div class="text-center mt-12">
+1 -3
View File
@@ -9,7 +9,7 @@ export interface SocialLinks {
twitter?: string;
bluesky?: string;
instagram?: string;
youTube?: string;
youtube?: string;
codetips?: string;
kofi?: string;
medium?: string;
@@ -23,7 +23,6 @@ export interface SiteConfig {
title: string;
description: string;
avatar: string;
location: string;
socialLinks: SocialLinks;
gpgKey?: string;
rssFeed?: string;
@@ -39,7 +38,6 @@ export const siteConfig: SiteConfig = {
description:
"Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
avatar: "/avatar.png",
location: "🇫🇷 France",
socialLinks: {
github: "https://github.com/anotherhadi",
gitlab: "https://gitlab.com/anotherhadi_mirror",
+2 -2
View File
@@ -55,8 +55,8 @@ Check for writable directories: you may be able to upload a webshell if FTP root
## Brute Force
```bash
hydra -l $user -P /usr/share/wordlists/rockyou.txt ftp://$IP
medusa -h $IP -u $user -P /usr/share/wordlists/rockyou.txt -M ftp
hydra -l $user -P ~/wordlists/rockyou.txt ftp://$IP
medusa -h $IP -u $user -P ~/wordlists/rockyou.txt -M ftp
```
Try default credentials first: `admin:admin`, `ftp:ftp`, `user:password`.
+46
View File
@@ -0,0 +1,46 @@
---
title: "RDP"
description: "Enumeration, exploitation and post-exploitation techniques for RDP servers."
tags: ["rdp", "network", "service"]
publishDate: 2026-05-04
---
## Overview
RDP (Remote Desktop Protocol) runs on **port 3389** and provides a graphical remote session.
Common on Windows servers and workstations.
## Enumeration
### Banner grabbing
```bash
nmap -sV -p 3389 $IP
nmap -p 3389 --script rdp-* $IP
```
Key scripts:
- `rdp-enum-encryption`: checks encryption level
- `rdp-vuln-ms12-020`: tests for MS12-020 DoS vulnerability
## Connect
```bash
xfreerdp /u:$user /p:$password /v:$IP
xfreerdp /u:$user /p:$password /v:$IP /cert:ignore
rdesktop $IP
```
Pass the hash directly (no plaintext password needed):
```bash
xfreerdp /u:$user /pth:$hash /v:$IP
```
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt rdp://$IP
crowbar -b rdp -s $IP/32 -u $user -C ~/wordlists/rockyou.txt
```
+75
View File
@@ -0,0 +1,75 @@
---
title: "SSH"
description: "Enumeration, exploitation and post-exploitation techniques for SSH servers."
tags: ["ssh", "network", "service"]
publishDate: 2026-05-04
---
## Overview
SSH runs on **port 22** and provides an encrypted remote shell.
Common implementations: OpenSSH, Dropbear, Bitvise.
## Enumeration
### Banner grabbing
```bash
nc -nv $IP 22
ssh $IP
```
The banner reveals the software and version (e.g. `OpenSSH_9.2`).
### Nmap
```bash
nmap -sV -p 22 $IP
nmap -p 22 --script ssh-* $IP
```
Key scripts:
- `ssh-hostkey`: retrieves the server's public key
- `ssh-auth-methods`: lists accepted authentication methods
- `ssh-brute`: brute-force credentials
## Connect
```bash
ssh $user@$IP
ssh -p 2222 $user@$IP
ssh -i id_rsa $user@$IP
```
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt ssh://$IP
medusa -h $IP -u $user -P ~/wordlists/rockyou.txt -M ssh
```
Only viable if password auth is enabled. Check with:
```bash
ssh -v $user@$IP
```
Look for `publickey,password` in the output.
## Key-Based Auth
If you find a private key (`id_rsa`), set permissions and connect:
```bash
chmod 600 id_rsa
ssh -i id_rsa $user@$IP
```
If the key is encrypted, crack the passphrase:
```bash
ssh2john id_rsa > hash.txt
john hash.txt --wordlist=~/wordlists/rockyou.txt
hashcat -m 22921 hash.txt ~/wordlists/rockyou.txt
```
+52
View File
@@ -0,0 +1,52 @@
---
title: "Telnet"
description: "Enumeration, exploitation and post-exploitation techniques for Telnet servers."
tags: ["telnet", "network", "service"]
publishDate: 2026-05-04
---
## Overview
Telnet runs on **port 23** and transmits all data (including credentials) in **cleartext**.
Common on embedded devices, legacy systems, routers, and IoT equipment.
## Enumeration
### Banner grabbing
```bash
nc -nv $IP 23
telnet $IP
```
The banner often reveals the OS, hostname, or device type.
### Nmap
```bash
nmap -sV -p 23 $IP
nmap -p 23 --script telnet-* $IP
```
Key scripts:
- `telnet-ntlm-info`: extracts NTLM info (Windows targets)
- `telnet-brute`: brute-force credentials
## Connect
```bash
telnet $IP
telnet $IP 23
```
Login with `user` / `password`. Session is fully interactive once authenticated.
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt telnet://$IP
medusa -h $IP -u $user -P ~/wordlists/rockyou.txt -M telnet
```
Try default credentials first. Routers and embedded devices commonly ship with `admin:admin`, `root:root`, or blank passwords.
@@ -0,0 +1,69 @@
---
title: "Information Gathering"
description: "Essential cybersecurity cheatsheet for Information Gathering and Open Source Intelligence (OSINT). Discover data related to emails, domains, usernames, and images using both command line and online tools."
tags: ["osint", "enumeration", "information-gathering"]
publishDate: 2026-05-03
---
**Information Gathering**, often referred to as **Open Source Intelligence (OSINT)** in the context of ethical hacking, is the systematic collection and analysis of publicly available data about a target, providing the foundational knowledge necessary to identify potential vulnerabilities and craft targeted security assessments.
## Command line tools
| **From** | **Use** |
| --------- | ----------------------------------------------------------------------------------------------- |
| Email | `holehe $email` |
| | `ghunt email $email` (for google account) |
| | `github-recon $email` ([link](http://github.com/anotherhadi/github-recon/), for github account) |
| Domain | `theHarvester -d $domain -l 100` |
| | `theHarvester -d $domain -l 100 -b all` (full) |
| Username | `sherlock $username` |
| Image | `exiftool $imagePath` |
| Instagram | `instaloader profile $username` |
| Github | `trufflehog github --org=$usernameOrOrg` |
| | `github-recon $username` ([link](http://github.com/anotherhadi/github-recon/)) |
## Online tools
| **For** | **Use** |
| ---------- | ------------------------------------------------------ |
| Visualiser | [OSINTracker](https://www.osintracker.com/) |
| IP | [Shodan](https://www.shodan.io/) |
| | [Censys](https://search.censys.io/) |
| Domain | [Whois](https://www.whois.com/whois/) |
| | [crt.sh](https://crt.sh/) (certificate transparency) |
| Name | [Webmii](https://webmii.com/) |
| | [BreachDirectory](https://breachdirectory.org/) |
| | [LeakLookup](https://leak-lookup.com/search) |
| | [IntelX](https://intelx.io/) |
| | [Genealogic.review](https://genealogic.review/) |
| SSID | [Wigle](https://wigle.net/) |
| Image | [PimEyes (faces)](https://pimeyes.com/) |
| | [Lenso (faces)](https://lenso.ai) |
| | [TinEye](https://tineye.com) |
| | [Pic2Map (exif geolocation)](https://www.pic2map.com/) |
| Username | [DeHashed](https://dehashed.com/search) |
| | [BreachDirectory](https://breachdirectory.org/) |
| | [IntelX](https://intelx.io/) |
| | [LeakLookup](https://leak-lookup.com/search) |
| | [Oathnet](https://oathnet.org/) |
| Email | [DeHashed](https://dehashed.com/search) |
| | [Hunter](https://hunter.io/) |
| | [HaveIBeenPwned](https://haveibeenpwned.com/) |
| | [BreachDirectory](https://breachdirectory.org/) |
| | [LeakLookup](https://leak-lookup.com/search) |
| | [IntelX](https://intelx.io/) |
| | [Oathnet](https://oathnet.org/) |
| Phone | [Epieos](https://epieos.com/) |
| Instagram | [Dumpor](https://dumpor.io/) |
| Misc | [Goosint](https://goosint.com/) |
| | [OSINT Framework](https://osintframework.com/) |
| | [OSINT Dojo](https://osintdojo.com/) |
## OSINT Aggregation Tool
<a href="https://iknowyou.hadi.icu" class="link-card not-prose" target="_blank">
<span>
<h4>IKnowYou</h4>
<p>Self-hosted OSINT aggregation platform: Run dozens of open-source intelligence tools against a single target in parallel; all from one clean web interface.</p>
</span>
</a>
+83
View File
@@ -0,0 +1,83 @@
---
title: "Sock Puppets"
description: "Essential cheatsheet on creating and managing Sock Puppets (fake identities) for ethical security research and Open Source Intelligence (OSINT), focusing on maintaining separation from personal data and bypassing common verification."
tags: ["osint", "sock-puppets"]
publishDate: 2026-05-03
---
Sock puppets are fake identities use to gather information from a target.
The sock puppet should have no link between your personal information and the fakes ones. (No ip address, mail, follow, etc..)
## Information generation
<a href="https://fakerjs.dev" class="link-card not-prose" target="_blank">
<span>
<h4>Faker</h4>
<p>Generate massive amounts of fake data</p>
</span>
</a>
<a href="https://fakenamegenerator.com/" class="link-card not-prose" target="_blank">
<span>
<h4>Fake Name</h4>
<p>Personal informations</p>
</span>
</a>
<a href="https://www.thispersondoesnotexist.com/" class="link-card not-prose" target="_blank">
<span>
<h4>This Person Does Not Exist</h4>
<p>Generate fake image</p>
</span>
</a>
## Bypass phone verification
<a href="https://www.smspool.net/" class="link-card not-prose" target="_blank">
<span>
<h4>SMSPool</h4>
<p>Cheapest and Fastest Online SMS verification</p>
</span>
</a>
<a href="https://receive-sms-online.info" class="link-card not-prose" target="_blank">
<span>
<h4>Receive Sms Online</h4>
<p>Free SMS verification</p>
</span>
</a>
<a href="https://receivefreesms.net" class="link-card not-prose" target="_blank">
<span>
<h4>Receive Free Sms</h4>
<p>Free SMS verification</p>
</span>
</a>
<a href="https://receive-smss.com" class="link-card not-prose" target="_blank">
<span>
<h4>Receive Free Sms</h4>
<p>Free SMS verification</p>
</span>
</a>
<a href="https://onlinesim.io/" class="link-card not-prose" target="_blank">
<span>
<h4>Online Sim</h4>
<p>SMS verification with free tier</p>
</span>
</a>
<a href="https://sms4stats.com/" class="link-card not-prose" target="_blank">
<span>
<h4>Sms 4 Sats</h4>
<p>Paid SMS verification</p>
</span>
</a>
<a href="http://sms4sat6y7lkq4vscloomatwyj33cfeddukkvujo2hkdqtmyi465spid.onion" class="link-card not-prose" target="_blank">
<span>
<h4>Sms 4 Sats (Onion)</h4>
<p>Paid SMS verification. Tor version</p>
</span>
</a>
+42
View File
@@ -0,0 +1,42 @@
---
title: "Tips"
description: "A cheatsheet of practical tips and unconventional methods for Open Source Intelligence (OSINT), focusing on advanced data visualization, information leakage detection, and utilizing web archives for historical data."
tags: ["osint"]
publishDate: 2026-05-03
---
## Visualisation
Use [OSINTracker](https://app.osintracker.com/) to visualise your findings.
It allows you to create a graph of your findings, which can help you see connections and relationships between different pieces of information.
## Forgotten passwords
To find email addresses and phone numbers associated with an account, you can click on "Forgot password?" on the login page of a website. Be careful, though, this creates notifications and can be detected by the target, and often gives your information away.
## Archive Search
- [Wayback Machine](https://web.archive.org) stores over 618 billion web captures
- [Archive.ph](https://archive.ph) creates on-demand snapshots, including for JS-heavy sites, with both a functional page and screenshot version
## Google Cache
Google keeps a cached version of most indexed pages. Access it with the `cache:` operator:
```
cache:example.com
cache:example.com/page
```
If the page has been taken down or modified, the cached version may still show the original content.
## Domain History
[VirusTotal](https://www.virustotal.com) shows the historical DNS records, subdomains, and associated IPs for any domain — useful when a site has moved or been taken down.
[ViewDNS.info](https://viewdns.info) covers WHOIS history, reverse IP, reverse MX, and port scans from a single interface.
## Bookmarklets
- [K2SOsint/Bookmarklets](https://github.com/K2SOsint/Bookmarklets)
- [tools.myosint.training](https://tools.myosint.training/)
+229 -117
View File
@@ -7,7 +7,7 @@
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
@@ -34,7 +34,7 @@
"fork": false,
"template": false,
"mirror": true,
"size": 429945,
"size": 430163,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages",
"html_url": "https://git.hadi.icu/anotherhadi/nixy",
@@ -47,13 +47,14 @@
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:31:04+02:00",
"updated_at": "2026-04-23T09:52:11+02:00",
"updated_at": "2026-05-07T09:47:53+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
@@ -90,7 +91,7 @@
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:25+02:00",
"mirror_updated": "2026-05-07T16:22:32+02:00",
"topics": [
"dotfiles",
"hyprland",
@@ -106,6 +107,220 @@
},
"banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png"
},
{
"id": 9,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "usbguard-tui",
"full_name": "anotherhadi/usbguard-tui",
"description": "A terminal UI for managing USB devices via usbguard. TUI built with golang & bubbletea.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 425,
"language": "Go",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui/languages",
"html_url": "https://git.hadi.icu/anotherhadi/usbguard-tui",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/usbguard-tui.git",
"clone_url": "https://git.hadi.icu/anotherhadi/usbguard-tui.git",
"original_url": "https://github.com/anotherhadi/usbguard-tui",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-04-30T17:40:42+02:00",
"updated_at": "2026-05-06T14:42:02+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-05-07T12:42:33+02:00",
"topics": [
"bubbletea",
"tui",
"usbguard"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/usbguard-tui",
"gitlab": "https://gitlab.com/anotherhadi_mirror/usbguard-tui"
},
"banner_url": "https://git.hadi.icu/anotherhadi/usbguard-tui/raw/branch/main/.github/assets/banner.png"
},
{
"id": 3,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "blog",
"full_name": "anotherhadi/blog",
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 3863,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
"html_url": "https://git.hadi.icu/anotherhadi/blog",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
"original_url": "https://github.com/anotherhadi/blog",
"website": "https://hadi.icu",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:39:47+02:00",
"updated_at": "2026-05-04T17:03:05+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-05-07T17:22:32+02:00",
"topics": [
"blog",
"cybersecurity",
"portfolio"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/blog",
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
},
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
},
{
"id": 6,
"owner": {
@@ -114,7 +329,7 @@
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
@@ -141,7 +356,7 @@
"fork": false,
"template": false,
"mirror": true,
"size": 676,
"size": 681,
"language": "Svelte",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages",
"html_url": "https://git.hadi.icu/anotherhadi/iknowyou",
@@ -154,6 +369,7 @@
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 2,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
@@ -197,7 +413,7 @@
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-04-23T14:17:25+02:00",
"mirror_updated": "2026-05-07T20:02:34+02:00",
"topics": [
"osint",
"osint-tool"
@@ -211,112 +427,6 @@
},
"banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png"
},
{
"id": 3,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "blog",
"full_name": "anotherhadi/blog",
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 3009,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
"html_url": "https://git.hadi.icu/anotherhadi/blog",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
"original_url": "https://github.com/anotherhadi/blog",
"website": "https://hadi.icu",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:39:47+02:00",
"updated_at": "2026-04-11T17:35:02+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:26+02:00",
"topics": [
"blog",
"cybersecurity",
"portfolio"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/blog",
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
},
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
},
{
"id": 2,
"owner": {
@@ -325,7 +435,7 @@
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
@@ -352,7 +462,7 @@
"fork": false,
"template": false,
"mirror": true,
"size": 530,
"size": 535,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages",
"html_url": "https://git.hadi.icu/anotherhadi/default-creds",
@@ -365,6 +475,7 @@
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
@@ -408,7 +519,7 @@
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:25+02:00",
"mirror_updated": "2026-05-07T17:12:33+02:00",
"topics": [
"cybersecurity",
"cybersecurity-tools",
@@ -432,7 +543,7 @@
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
@@ -472,6 +583,7 @@
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
+1 -8
View File
@@ -6,6 +6,7 @@ import BackToTop from "../components/BackToTop.astro";
import { ChevronLeft } from "@lucide/astro";
import { parse } from "node-html-parser";
import Author from "../components/Author.astro";
import { formatDate } from "../utils/notes";
interface Props {
title: string;
@@ -19,14 +20,6 @@ interface Props {
const { title, description, publishDate, updatedDate, image, tags } =
Astro.props;
function formatDate(date: Date) {
return date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
});
}
const content = await Astro.slots.render("default");
const wordCount = content.split(/\s+/).length;
const readingTime = Math.ceil(wordCount / 200);
+5 -27
View File
@@ -15,26 +15,11 @@ const {
description = "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
} = Astro.props;
// Custom blur-fade animation configuration
const blurFadeAnimation = {
old: {
name: "blurFadeOut",
duration: "0.1s",
easing: "ease-in-out",
fillMode: "forwards",
},
new: {
name: "blurFadeIn",
duration: "0.1s",
easing: "ease-in-out",
fillMode: "backwards",
},
};
const pageTransition = {
forwards: blurFadeAnimation,
backwards: blurFadeAnimation,
const anim = {
old: { name: "blurFadeOut", duration: "0.1s", easing: "ease-in-out", fillMode: "forwards" },
new: { name: "blurFadeIn", duration: "0.1s", easing: "ease-in-out", fillMode: "backwards" },
};
const pageTransition = { forwards: anim, backwards: anim };
const origin = Astro.url.origin;
---
@@ -49,23 +34,19 @@ const origin = Astro.url.origin;
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<!-- View Transitions -->
<ClientRouter />
<!-- Open Graph / Social Media -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={origin} />
<meta property="og:image" content={`${origin}/images/og_home.png`} />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={`${origin}/images/og_home.png`} />
<!-- RSS Feed -->
<link
rel="alternate"
type="application/rss+xml"
@@ -87,13 +68,11 @@ const origin = Astro.url.origin;
<Oneko />
<Console />
<!-- Smooth Scroll -->
<style is:global>
html {
scroll-behavior: smooth;
}
/* Initial Page Load Blur-Fade Animation */
@keyframes pageLoadBlurFade {
0% {
opacity: 0;
@@ -106,11 +85,10 @@ const origin = Astro.url.origin;
}
html {
animation: pageLoadBlurFade 0.3s ease-in-out;
animation: pageLoadBlurFade 0.1s ease-in-out;
animation-fill-mode: both;
}
/* Blur Fade View Transitions (for page-to-page navigation) */
@keyframes blurFadeIn {
0% {
opacity: 0;
-229
View File
@@ -1,229 +0,0 @@
---
import Layout from "./Layout.astro";
import { Image } from "astro:assets";
import TagBadge from "../components/TagBadge.astro";
import { ChevronLeft, ExternalLink } from "@lucide/astro";
interface Props {
title: string;
description: string;
image: any;
tags: string[];
demoLink?: string;
url?: string;
sourceLink?: string;
}
const { title, description, image, tags, demoLink, url, sourceLink } =
Astro.props;
---
<Layout title={`${title} - Another Hadi`} description={description}>
<article class="max-w-4xl mx-auto px-4 py-20">
<!-- Back button -->
<div class="mb-8 flex flex-wrap justify-between gap-5">
<a href="/projects" class="btn btn-ghost btn-sm">
<ChevronLeft size={18} />
Back to Projects
</a>
</div>
<!-- Featured Image -->
<!-- TODO: Future Enhancement - Support multiple images/project gallery -->
{
image && (
<figure class="mb-8 rounded-2xl overflow-hidden">
<Image
src={image}
alt={title}
class="w-full aspect-video object-cover"
width={1200}
height={630}
/>
</figure>
)
}
<!-- Project Header -->
<header class="mb-8">
<h1 class="text-5xl font-bold mb-4">{title}</h1>
<p class="text-xl text-base-content/70 mb-6">{description}</p>
<!-- Prominent Action Buttons -->
{
(demoLink || sourceLink) && (
<div class="flex flex-wrap gap-3 mb-6">
{demoLink && (
<a
href={demoLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-primary gap-2"
>
<ExternalLink class="size-5" />
Live Demo
</a>
)}
{url && (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="btn btn-soft gap-2"
>
<ExternalLink class="size-4" />
Website
</a>
)}
{sourceLink && (
<a
href={sourceLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-soft gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M16,2.345c7.735,0,14,6.265,14,14-.002,6.015-3.839,11.359-9.537,13.282-.7,.14-.963-.298-.963-.665,0-.473,.018-1.978,.018-3.85,0-1.312-.437-2.152-.945-2.59,3.115-.35,6.388-1.54,6.388-6.912,0-1.54-.543-2.783-1.435-3.762,.14-.35,.63-1.785-.14-3.71,0,0-1.173-.385-3.85,1.435-1.12-.315-2.31-.472-3.5-.472s-2.38,.157-3.5,.472c-2.677-1.802-3.85-1.435-3.85-1.435-.77,1.925-.28,3.36-.14,3.71-.892,.98-1.435,2.24-1.435,3.762,0,5.355,3.255,6.563,6.37,6.913-.403,.35-.77,.963-.893,1.872-.805,.368-2.818,.963-4.077-1.155-.263-.42-1.05-1.452-2.152-1.435-1.173,.018-.472,.665,.017,.927,.595,.332,1.277,1.575,1.435,1.978,.28,.787,1.19,2.293,4.707,1.645,0,1.173,.018,2.275,.018,2.607,0,.368-.263,.787-.963,.665-5.719-1.904-9.576-7.255-9.573-13.283,0-7.735,6.265-14,14-14Z" />
</svg>
View Source
</a>
)}
</div>
)
}
</header>
<!-- Tags -->
{
tags && tags.length > 0 && (
<div class="flex flex-wrap gap-2 mt-4">
{tags.map((tag) => (
<TagBadge tag={tag} />
))}
</div>
)
}
<!-- Divider -->
<div class="divider"></div>
<!-- Project Content -->
<div class="prose-content max-w-none">
<slot />
</div>
<!-- Divider -->
<div class="divider mt-12"></div>
<!-- Back to projects link -->
<div class="flex justify-center gap-2 mt-12">
<div class="flex gap-3 justify-center flex-wrap text-sm">
<a href="/projects" class="link link-hover">View All Projects</a>
<span class="text-base-content/30">•</span>
<a href="/#contact" class="link link-hover">Contact me</a>
<span class="text-base-content/30">•</span>
<a href="https://ko-fi.com/anotherhadi" class="link link-hover"
>Support me</a
>
</div>
</div>
</article>
</Layout>
<style is:global>
.prose-content {
color: inherit;
line-height: 1.75;
}
.prose-content h1,
.prose-content h2,
.prose-content h3,
.prose-content h4,
.prose-content h5,
.prose-content h6 {
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
}
.prose-content h1 {
font-size: 2.25rem;
}
.prose-content h2 {
font-size: 1.875rem;
}
.prose-content h3 {
font-size: 1.5rem;
}
.prose-content p {
margin-bottom: 1rem;
}
.prose-content a {
text-decoration: underline;
}
.prose-content ul,
.prose-content ol {
margin-bottom: 1rem;
margin-left: 1.5rem;
list-style-position: outside;
}
.prose-content ul {
list-style-type: disc;
}
.prose-content ol {
list-style-type: decimal;
}
.prose-content li {
margin-bottom: 0.5rem;
}
.prose-content code {
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-family: ui-monospace, monospace;
}
.prose-content pre {
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1rem;
}
.prose-content pre code {
background: transparent;
padding: 0;
}
.prose-content blockquote {
border-left-width: 4px;
padding-left: 1rem;
font-style: italic;
margin: 1rem 0;
}
.prose-content img {
border-radius: 0.5rem;
margin: 1.5rem 0;
}
.prose-content strong {
font-weight: 600;
}
</style>
+6 -1
View File
@@ -45,7 +45,12 @@ const sortedPosts = blogPosts.sort(
) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{sortedPosts.map((post) => (
<BlogCard post={post} />
<BlogCard
displayBanner={true}
displayTags={true}
displayDate={true}
post={post}
/>
))}
</div>
)
+7 -2
View File
@@ -3,26 +3,31 @@ import Layout from "../layouts/Layout.astro";
import Hero from "../components/Hero.astro";
import Projects from "../components/Projects.astro";
import Blog from "../components/Blog.astro";
import Notes from "../components/Notes.astro";
import Contact from "../components/Contact.astro";
import { siteConfig } from "../config";
import avatar from "../../public/avatar.jpg";
---
<Layout title={`Another Hadi`} description={siteConfig.description}>
<main>
<main class="px-10">
<Hero
name={siteConfig.name}
title={siteConfig.title}
description={siteConfig.description}
avatar={avatar}
location={siteConfig.location}
socialLinks={siteConfig.socialLinks}
gpgKey={siteConfig.gpgKey}
rssFeed={siteConfig.rssFeed}
/>
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Blog />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Notes />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Projects />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Contact />
</main>
</Layout>
+37 -74
View File
@@ -3,10 +3,10 @@ import { getCollection, render } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import { List, PanelRight } from "@lucide/astro";
import NoteTOC from "../../components/NoteTOC.astro";
import NoteNavSidebar from "../../components/NoteNavSidebar.svelte";
import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
import NoteGraphSidebar from "../../components/NoteGraphSidebar.astro";
import NoteVars from "../../components/NoteVars.svelte";
import { getCategory, extractLinks, extractHeadings } from "../../utils/notes";
import { getCategory, extractLinks, extractExternalLinks } from "../../utils/notes";
export async function getStaticPaths() {
const notes = await getCollection("notes");
@@ -17,7 +17,7 @@ export async function getStaticPaths() {
}
const { entry } = Astro.props;
const { Content } = await render(entry);
const { Content, headings: astroHeadings } = await render(entry);
const allNotes = await getCollection("notes");
const sortedNotes = allNotes.sort((a, b) =>
@@ -60,15 +60,23 @@ const noteVars = [
),
];
const headings = extractHeadings(entry.body ?? "");
const headings = astroHeadings.map((h) => ({ depth: h.depth, text: h.text, id: h.slug }));
const externalLinks = extractExternalLinks(entry.body ?? "");
---
<style>
.drawer.lg\:drawer-open > .drawer-side,
@media (min-width: 768px) {
.drawer.md\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
}
@media (min-width: 1280px) {
.drawer.xl\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
}
</style>
<Layout
@@ -76,13 +84,13 @@ const headings = extractHeadings(entry.body ?? "");
description={entry.data.description}
>
<main class="max-w-screen-2xl mx-auto">
<div class="drawer drawer-end xl:drawer-open min-h-[calc(100vh-3rem)]">
<input id="graph-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex min-h-[calc(100vh-3rem)]">
<div class="drawer lg:drawer-open w-full">
<div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex min-h-[calc(100vh-3rem)] min-w-0">
<div class="drawer drawer-end xl:drawer-open w-full" id="right-drawer">
<input id="graph-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col min-w-0">
<main class="flex-1 px-4 sm:px-6 lg:px-10 py-6 lg:py-10 min-w-0">
<div class="max-w-3xl mx-auto lg:mx-0">
@@ -115,7 +123,7 @@ const headings = extractHeadings(entry.body ?? "");
<div class="flex items-center gap-2">
<label
for="nav-drawer"
class="btn btn-ghost btn-xs lg:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
>
<List size={11} />
nav
@@ -124,7 +132,7 @@ const headings = extractHeadings(entry.body ?? "");
<label
for="graph-drawer"
id="graph-toggle"
class="btn btn-ghost btn-xs font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
class="btn btn-ghost btn-xs xl:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
title="Toggle graph"
>
<PanelRight size={11} />
@@ -204,32 +212,29 @@ const headings = extractHeadings(entry.body ?? "");
</main>
</div>
<div class="drawer-side z-50">
<label
for="nav-drawer"
aria-label="close sidebar"
class="drawer-overlay"></label>
<NoteNavSidebar
client:load
notes={sortedNotes}
currentEntry={entry}
categories={categories}
/>
</div>
</div>
</div>
<div class="drawer-side z-40">
<div class="drawer-side z-[60]">
<label
for="graph-drawer"
aria-label="close sidebar"
class="drawer-overlay xl:hidden"></label>
class="drawer-overlay"></label>
<NoteGraphSidebar
entry={entry}
graphNodes={graphNodes}
graphEdges={graphEdges}
forwardLinks={forwardLinks}
backlinks={backlinks}
externalLinks={externalLinks}
/>
</div>
</div>
</div>
<div class="drawer-side z-[70]">
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<NoteNavSidebar
notes={sortedNotes}
currentEntry={entry}
categories={categories}
/>
</div>
</div>
@@ -242,7 +247,9 @@ const headings = extractHeadings(entry.body ?? "");
const s = document.createElement("style");
s.id = "heading-anchor-styles";
s.textContent = `
.note-content h2, .note-content h3, .note-content h4 {
.note-content h2:not(.link-card h2),
.note-content h3:not(.link-card h3),
.note-content h4:not(.link-card h4) {
display: flex !important;
align-items: center;
flex-wrap: wrap;
@@ -288,51 +295,7 @@ const headings = extractHeadings(entry.body ?? "");
});
}
function initXlGraphToggle() {
const graphDrawer = document.getElementById(
"graph-drawer",
) as HTMLInputElement | null;
if (!graphDrawer) return;
const outerDrawer = graphDrawer.closest<HTMLElement>(".drawer.drawer-end");
const xlQuery = window.matchMedia("(min-width: 1280px)");
const STORAGE_KEY = "notes-graph-sidebar";
function setXlSidebar(open: boolean) {
if (!outerDrawer) return;
if (open) {
outerDrawer.classList.add("xl:drawer-open");
} else {
outerDrawer.classList.remove("xl:drawer-open");
}
localStorage.setItem(STORAGE_KEY, open ? "1" : "0");
}
const graphToggle = document.getElementById("graph-toggle");
graphToggle?.addEventListener("click", (e) => {
if (!xlQuery.matches) return;
e.preventDefault();
setXlSidebar(!outerDrawer?.classList.contains("xl:drawer-open"));
});
if (xlQuery.matches) {
const saved = localStorage.getItem(STORAGE_KEY);
// Open by default unless user explicitly closed it
setXlSidebar(saved !== "0");
}
xlQuery.addEventListener("change", (e) => {
if (!e.matches) {
outerDrawer?.classList.remove("xl:drawer-open");
} else {
const saved = localStorage.getItem(STORAGE_KEY);
setXlSidebar(saved !== "0");
}
});
}
document.addEventListener("astro:page-load", () => {
injectHeadingAnchors();
initXlGraphToggle();
});
</script>
+8 -10
View File
@@ -1,7 +1,7 @@
---
import { getCollection } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import NoteNavSidebar from "../../components/NoteNavSidebar.svelte";
import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
import { getCategory } from "../../utils/notes";
import { List } from "@lucide/astro";
@@ -41,10 +41,12 @@ if (!categoryNotes) {
---
<style>
.drawer.lg\:drawer-open > .drawer-side {
@media (min-width: 768px) {
.drawer.md\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
}
</style>
<Layout
@@ -52,7 +54,7 @@ if (!categoryNotes) {
description={`Notes on ${category}.`}
>
<main class="max-w-screen-2xl mx-auto">
<div class="drawer lg:drawer-open min-h-[calc(100vh-3rem)]">
<div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col min-w-0">
@@ -72,7 +74,7 @@ if (!categoryNotes) {
</div>
<label
for="nav-drawer"
class="btn btn-ghost btn-xs lg:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
>
<List size={11} />
nav
@@ -161,13 +163,9 @@ if (!categoryNotes) {
</main>
</div>
<div class="drawer-side z-50">
<label
for="nav-drawer"
aria-label="close sidebar"
class="drawer-overlay"></label>
<div class="drawer-side z-[70]">
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<NoteNavSidebar
client:load
notes={allNotes}
currentCategory={category}
categories={categories}
+1 -1
View File
@@ -36,7 +36,7 @@ import repos from "../../data/repos.json";
) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{repos.map((repo) => (
<GiteaProjectCard repo={repo} />
<GiteaProjectCard displayBanner={true} repo={repo} />
))}
</div>
)
+65
View File
@@ -36,6 +36,71 @@
--noise: 0;
}
.drawer-side > aside > astro-island {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.btn:not(.btn-circle):not(.btn-square) {
@apply rounded-lg;
}
@layer components {
.link-card {
display: flex;
align-items: center;
padding: 0 0.875rem;
border-radius: var(--radius-box);
border: 1px solid oklch(24% 0 0);
background: transparent;
color: var(--color-base-content);
text-decoration: none !important;
transition:
background 0.15s ease,
border-color 0.15s ease;
margin-block: 0.25rem;
}
.link-card::after {
content: "↗";
margin-left: auto;
padding-left: 0.75rem;
opacity: 0;
color: var(--color-primary);
font-size: 0.75rem;
transition:
opacity 0.15s ease,
transform 0.15s ease;
transform: translate(-4px, 4px);
flex-shrink: 0;
}
.link-card:hover {
background: var(--color-base-200);
border-color: var(--color-primary);
}
.link-card:hover::after {
opacity: 1;
transform: translate(0, 0);
}
.link-card span {
display: flex;
flex-direction: column;
}
.link-card h4 {
font-size: 0.85rem;
font-weight: 600;
margin: 0;
line-height: 1;
transition: color 0.15s ease;
}
.link-card:hover h4 {
color: var(--color-primary);
}
.link-card p {
font-size: 0.75rem;
margin: 0;
line-height: 1;
color: oklch(52% 0 0);
}
}
+30 -11
View File
@@ -7,16 +7,7 @@ export function getCategory(n: {
return parts.length > 1 ? parts[0] : "General";
}
export function extractInlineHashtags(body: string): string[] {
const re = /#(\w+)/g;
const tags: string[] = [];
let m;
while ((m = re.exec(body)) !== null) tags.push(m[1].toLowerCase());
return [...new Set(tags)];
}
// Mirrors github-slugger: keeps _, keeps unicode letters/numbers, spaces → hyphens
export function slugify(text: string): string {
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\p{L}\p{N}\s_-]/gu, "")
@@ -24,6 +15,34 @@ export function slugify(text: string): string {
.replace(/ +/g, "-");
}
export function extractExternalLinks(body: string): { url: string; label: string }[] {
const seen = new Set<string>();
const links: { url: string; label: string }[] = [];
// Markdown: [label](https://...)
const mdRe = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
let m;
while ((m = mdRe.exec(body)) !== null) {
if (!seen.has(m[2])) {
seen.add(m[2]);
links.push({ url: m[2], label: m[1] });
}
}
// HTML: <a href="https://...">...</a> — use h4 content as label if present, else href host
const htmlRe = /<a\s[^>]*href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
while ((m = htmlRe.exec(body)) !== null) {
const url = m[1];
if (seen.has(url)) continue;
seen.add(url);
const h4 = m[2].match(/<h4[^>]*>([\s\S]*?)<\/h4>/);
const label = h4 ? h4[1].trim() : new URL(url).hostname;
links.push({ url, label });
}
return links;
}
export function extractLinks(body: string): string[] {
const re = /\(\/notes\/([^)#\s]+)(?:#[^)\s]*)?\)/g;
const ids: string[] = [];
@@ -34,7 +53,7 @@ export function extractLinks(body: string): string[] {
export function formatDate(date: Date): string {
return date.toLocaleDateString("en-US", {
month: "short",
month: "long",
day: "numeric",
year: "numeric",
});