chore: remove deprecated files and update configuration for improved SEO and performance. Adjust font usage in styles, enhance sitemap handling, and refine search functionality.

This commit is contained in:
cojocaru-david
2025-08-13 16:47:34 +03:00
parent ebd7d354a0
commit 39e42e5589
26 changed files with 1810 additions and 3298 deletions

View File

@@ -18,15 +18,39 @@ import rehypeDocument from 'rehype-document'
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections' import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers' import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from "@tailwindcss/vite";
import vercel from '@astrojs/vercel'; import vercel from '@astrojs/vercel';
function rehypeDemoteH1AndStripTitle() {
return (tree: any) => {
const walk = (node: any, parent: any | null, indexInParent: number | null) => {
if (!node) return
const isElement = node.type === 'element'
if (isElement) {
if (node.tagName === 'title') {
if (parent && Array.isArray(parent.children) && indexInParent !== null && indexInParent > -1) {
parent.children.splice(indexInParent, 1)
return
}
}
if (node.tagName === 'h1') {
node.tagName = 'h2'
}
}
if (Array.isArray(node.children)) {
for (let i = node.children.length - 1; i >= 0; i--) {
walk(node.children[i], node, i)
}
}
}
walk(tree, null, null)
}
}
export default defineConfig({ export default defineConfig({
site: 'https://www.cojocarudavid.me', site: 'https://www.cojocarudavid.me',
integrations: [ integrations: [expressiveCode({
expressiveCode({
themes: ['catppuccin-latte', 'ayu-dark'], themes: ['catppuccin-latte', 'ayu-dark'],
plugins: [pluginCollapsibleSections(), pluginLineNumbers()], plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
useDarkModeMediaQuery: true, useDarkModeMediaQuery: true,
@@ -40,15 +64,10 @@ export default defineConfig({
}, },
}, },
}, },
}), }), mdx(), react(), sitemap(), icon()],
mdx(),
react(),
sitemap(),
icon(),
],
vite: { vite: {
plugins: [tailwindcss()], plugins: [tailwindcss() as any],
optimizeDeps: { optimizeDeps: {
exclude: ["satori", "satori-html"], exclude: ["satori", "satori-html"],
include: [ include: [
@@ -93,6 +112,7 @@ export default defineConfig({
rel: ['nofollow', 'noreferrer', 'noopener'], rel: ['nofollow', 'noreferrer', 'noopener'],
}, },
], ],
rehypeDemoteH1AndStripTitle,
rehypeHeadingIds, rehypeHeadingIds,
rehypeKatex, rehypeKatex,
[ [

1908
bun.lock

File diff suppressed because it is too large Load Diff

2658
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,8 +23,8 @@
"version": "1.0.6", "version": "1.0.6",
"private": false, "private": false,
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev --port 3010",
"start": "astro dev", "start": "astro preview",
"build": "astro check && astro build", "build": "astro check && astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
@@ -32,17 +32,17 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@astrojs/markdown-remark": "^6.3.3", "@astrojs/markdown-remark": "^6.3.5",
"@astrojs/mdx": "^4.3.1", "@astrojs/mdx": "^4.3.3",
"@astrojs/react": "^4.3.0", "@astrojs/react": "^4.3.0",
"@astrojs/rss": "^4.0.12", "@astrojs/rss": "^4.0.12",
"@astrojs/sitemap": "^3.4.2", "@astrojs/sitemap": "^3.4.2",
"@astrojs/vercel": "^8.2.3", "@astrojs/vercel": "^8.2.5",
"@expressive-code/plugin-collapsible-sections": "^0.41.3", "@expressive-code/plugin-collapsible-sections": "^0.41.3",
"@expressive-code/plugin-line-numbers": "^0.41.3", "@expressive-code/plugin-line-numbers": "^0.41.3",
"@fingerprintjs/fingerprintjs": "^4.6.2", "@fingerprintjs/fingerprintjs": "^4.6.2",
"@iconify-json/line-md": "^1.2.8", "@iconify-json/line-md": "^1.2.11",
"@iconify-json/lucide": "^1.2.59", "@iconify-json/lucide": "^1.2.62",
"@iconify-json/mdi": "^1.2.3", "@iconify-json/mdi": "^1.2.3",
"@neondatabase/serverless": "^1.0.1", "@neondatabase/serverless": "^1.0.1",
"@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-avatar": "^1.1.10",
@@ -53,11 +53,11 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@resvg/resvg-js": "^2.6.2", "@resvg/resvg-js": "^2.6.2",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@types/react": "19.1.9", "@types/react": "19.1.10",
"@types/react-dom": "19.1.7", "@types/react-dom": "19.1.7",
"@vercel/routing-utils": "^5.1.1", "@vercel/routing-utils": "^5.1.1",
"@vercel/speed-insights": "^1.2.0", "@vercel/speed-insights": "^1.2.0",
"astro": "^5.12.6", "astro": "^5.12.9",
"astro-expressive-code": "^0.41.3", "astro-expressive-code": "^0.41.3",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -65,7 +65,7 @@
"framer-motion": "^12.23.12", "framer-motion": "^12.23.12",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lucide-react": "^0.534.0", "lucide-react": "^0.539.0",
"react": "19.1.1", "react": "19.1.1",
"react-dom": "19.1.1", "react-dom": "19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
@@ -79,7 +79,7 @@
"satori": "^0.16.2", "satori": "^0.16.2",
"satori-html": "^0.3.2", "satori-html": "^0.3.2",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"typescript": "^5.8.3" "typescript": "^5.9.2"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -46,12 +46,7 @@ import Favicons from './Favicons.astro'
/> />
<meta name="msapplication-TileColor" content="#121212" /> <meta name="msapplication-TileColor" content="#121212" />
<!-- Prefetch and Preconnect for Performance --> <!-- Feed link -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
<!-- Sitemap and Feed Links -->
<link rel="sitemap" href="/sitemap-index.xml" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<link <link
rel="alternate" rel="alternate"

View File

@@ -10,18 +10,20 @@ const { title = SITE.title, description = SITE.description } = Astro.props
const image = new URL('/ogImage.png', Astro.site).toString() const image = new URL('/ogImage.png', Astro.site).toString()
const posts = await getAllPosts() const posts = await getAllPosts()
// Optimize description for SEO // Optimize description for SEO (50-155 chars)
const optimizedDescription = description.length > 160 const descTrimmed = description.trim()
? description.substring(0, 157) + '...' const optimizedDescription = descTrimmed.length > 155
: description ? descTrimmed.slice(0, 152) + '...'
: descTrimmed
// Create proper page title // Create proper page title, clamp to ~60 chars
const pageTitle = title === SITE.title ? SITE.title : `${title} | ${SITE.title}` const rawTitle = title === SITE.title ? SITE.title : `${title} | ${SITE.title}`
const pageTitle = rawTitle.length > 60 ? rawTitle.slice(0, 57) + '...' : rawTitle
--- ---
<title>{pageTitle}</title> <title>{pageTitle}</title>
<meta name="description" content={optimizedDescription} /> <meta name="description" content={optimizedDescription} />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" /> <!-- robots set globally in base head to avoid duplicates -->
<meta name="language" content={SITE.locale} /> <meta name="language" content={SITE.locale} />
<link rel="canonical" href={Astro.url} /> <link rel="canonical" href={Astro.url} />

View File

@@ -14,19 +14,22 @@ const postUrl = new URL(`/blog/${post.id}/`, SITE.href).toString()
const image = new URL(`/image/${post.id}.png`, SITE.href).toString() const image = new URL(`/image/${post.id}.png`, SITE.href).toString()
const author = post.data.authors ? post.data.authors.join(', ') : SITE.author const author = post.data.authors ? post.data.authors.join(', ') : SITE.author
const optimizedDescription = description.length > 160 // Optimize description for SEO (50-155 chars)
? description.substring(0, 157) + '...' const descTrimmed = description.trim()
: description const optimizedDescription = descTrimmed.length > 155
? descTrimmed.slice(0, 152) + '...'
: descTrimmed
const seoTitle = `${title} - ${SITE.title}` // Compose seo title, clamp near 60 chars
const baseSeoTitle = `${title} - ${SITE.title}`
const seoTitle = baseSeoTitle.length > 60 ? baseSeoTitle.slice(0, 57) + '...' : baseSeoTitle
const postUrlWithoutTrailingSlash = postUrl.endsWith('/') ? postUrl.slice(0, -1) : postUrl const postUrlWithoutTrailingSlash = postUrl.endsWith('/') ? postUrl.slice(0, -1) : postUrl
const postCanonicalUrl = new URL(postUrlWithoutTrailingSlash, SITE.href).toString() const postCanonicalUrl = new URL(postUrlWithoutTrailingSlash, SITE.href).toString()
--- ---
<title>{seoTitle}</title> <title>{seoTitle}</title>
<meta name="description" content={optimizedDescription} /> <meta name="description" content={optimizedDescription} />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- viewport and robots are set globally in base head -->
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
<meta name="author" content={author} /> <meta name="author" content={author} />
<meta name="publisher" content={SITE.title} /> <meta name="publisher" content={SITE.title} />
<meta name="language" content={SITE.locale} /> <meta name="language" content={SITE.locale} />

View File

@@ -1,59 +0,0 @@
---
---
<script is:inline>
!(function (t, e) {
var o, n, p, r;
e.__SV ||
((window.posthog = e),
(e._i = []),
(e.init = function (i, s, a) {
function g(t, e) {
var o = e.split(".");
2 == o.length && ((t = t[o[0]]), (e = o[1])),
(t[e] = function () {
t.push([e].concat(Array.prototype.slice.call(arguments, 0)));
});
}
((p = t.createElement("script")).type = "text/javascript"),
(p.crossOrigin = "anonymous"),
(p.async = !0),
(p.src =
s.api_host.replace(".i.posthog.com", "-assets.i.posthog.com") +
"/static/array.js"),
(r = t.getElementsByTagName("script")[0]).parentNode.insertBefore(
p,
r
);
var u = e;
for (
void 0 !== a ? (u = e[a] = []) : (a = "posthog"),
u.people = u.people || [],
u.toString = function (t) {
var e = "posthog";
return (
"posthog" !== a && (e += "." + a), t || (e += " (stub)"), e
);
},
u.people.toString = function () {
return u.toString(1) + ".people (stub)";
},
o =
"init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug getPageViewId captureTraceFeedback captureTraceMetric".split(
" "
),
n = 0;
n < o.length;
n++
)
g(u, o[n]);
e._i.push([i, s, a]);
}),
(e.__SV = 1));
})(document, window.posthog || []);
posthog.init("phc_orIpAm9v5xuxdhe4wd5FhkyTJ7ygykslwxxeCESZquz", {
api_host: "https://eu.i.posthog.com",
person_profiles: "identified_only",
});
</script>

View File

@@ -24,9 +24,11 @@ function Search({ searchList, initialPosts }) {
...item, ...item,
data: { data: {
...item.data, ...item.data,
title: item.data.title.toLowerCase(), title: String(item.data.title || '').toLowerCase(),
description: item.data.description.toLowerCase(), description: String(item.data.description || '').toLowerCase(),
tags: item.data.tags.map((tag) => tag.toLowerCase()), tags: Array.isArray(item.data.tags)
? item.data.tags.map((tag) => String(tag).toLowerCase())
: [],
}, },
})), })),
[searchList], [searchList],

View File

@@ -1,75 +1,176 @@
--- ---
title: "Zero trust security: the ultimate guide for businesses" title: "Zero Trust Security: How to Roll It Out in 90 Days Without Breaking Your Budget (2025 Guide)"
description: "Explore zero trust security: the ultimate guide for businesses in this detailed guide, offering insights, strategies, and practical tips to enhance your understanding and application of the topic." description: "Step-by-step Zero Trust security implementation for 2025. Real costs, tools, and a 90-day roadmap that actually works for remote teams and cloud apps."
date: 2025-04-26 date: 2025-04-26
tags: ["zero", "trust", "security", "ultimate", "guide", "businesses"] tags:
authors: ["Cojocaru David", "ChatGPT"] - "zero trust security"
- "step by step zero trust"
- "cybersecurity roadmap"
- "remote work security"
- "cloud security"
- "small business zero trust"
- "mfa setup"
- "micro-segmentation guide"
authors:
- "Cojocaru David"
- "ChatGPT"
slug: "zero-trust-security-step-by-step-implementation-guide-2025"
updatedDate: 2025-08-13
--- ---
# Zero Trust Security: The Ultimate Guide for Businesses Hey friend, ever feel like your office firewall is just a fancy welcome mat for hackers? Last month, a buddy of mine watched a fake Zoom invite steal every password in his 50-person startup. **Ouch.** Thats why were talking Zero Trust today.
In todays rapidly evolving digital landscape, traditional security models are no longer sufficient to protect businesses from sophisticated cyber threats. **Zero Trust Security: The Ultimate Guide for Businesses** explores this modern approach to cybersecurity, which operates on the principle of "never trust, always verify." Whether you're a small business or a large enterprise, adopting Zero Trust can significantly reduce your risk of breaches and data loss. Heres the deal. By the end of this guide youll know **exactly** how to:
* ditch the old “castle-and-moat” mindset
* protect laptops, phones, and cloud apps **without** buying a spaceship
* finish in 90 days—budget friendly, user-friendly, CFO-approved
This guide will break down what Zero Trust is, why it matters, and how to implement it effectively. Ready? Grab your coffee. Lets go.
## What Is Zero Trust Security? ## What Zero Trust Really Means (Spoiler: Its Not Paranoia)
Zero Trust Security is a framework that eliminates the concept of trust from an organizations network architecture. Unlike traditional models that assume everything inside a network is safe, Zero Trust requires continuous verification of every user, device, and application—regardless of location. Zero Trust boils down to **“never trust, always verify.”** Think of it like a nightclub bouncer who checks your ID **every single time** you go to the bathroom—even if he just stamped your hand.
### Core Principles of Zero Trust **Traditional Model**: Inside the building = safe
- **Least Privilege Access:** Grant users only the permissions they need. **Zero Trust Model**: Every click, tap, or download gets a fresh ID check, even if youre the CEO on your own laptop.
- **Micro-Segmentation:** Divide networks into smaller, isolated zones to limit lateral movement.
- **Continuous Monitoring:** Constantly validate security configurations and user behavior.
- **Multi-Factor Authentication (MFA):** Require multiple forms of verification before granting access.
## Why Businesses Need Zero Trust Security ### Quick Head-to-Head
Cyberattacks are becoming more frequent and sophisticated, with ransomware, phishing, and insider threats posing significant risks. Heres why Zero Trust is essential: | Old Way | Zero Trust Way |
|---|---|
| VPN lets anyone inside | Every request is verified |
| One password to rule them all | MFA + device health checks |
| Flat network (like a big open office) | Micro-segments (private cubicles with locks) |
- **Rise of Remote Work:** Employees accessing systems from various locations increase vulnerability. ## Why Zero Trust Matters in 2025 (Real Numbers)
- **Cloud Adoption:** Data stored across multiple cloud services requires stricter access controls.
- **Regulatory Compliance:** Zero Trust helps meet GDPR, HIPAA, and other data protection standards.
> *"Trust is a vulnerability. Zero Trust is the solution."* — John Kindervag, Creator of Zero Trust **Quick stats that keep me up at night:**
* Ransomware hits **every 11 seconds** (Cybersecurity Ventures)
* **83 % of breaches** start with stolen or weak passwords (Verizon DBIR)
* Average company now juggles **1,295 cloud apps** (Netskope). Firewalls? They cant even see most of them.
## Key Components of a Zero Trust Framework Oh, and your team? **70 % works remotely at least part-time.** VPNs buckle under that load. Zero Trust doesnt flinch.
Implementing Zero Trust involves multiple layers of security. Here are the critical components: ## The 5-Layer Stack You Actually Need
### 1. Identity Verification Lets cut the fluff. You need five things. Thats it.
- Use MFA and biometric authentication.
- Implement role-based access control (RBAC).
### 2. Device Security ### 1. Identity & Access Management (IAM)
- Ensure all devices meet security standards before granting access. - **MFA everywhere**—start with free Microsoft Authenticator or Google Authenticator
- Regularly update and patch software. - **Single Sign-On**—one password, many apps (Azure AD, Okta, JumpCloud)
- **Context rules**—block logins from North Korea at 3 a.m. when youre in Texas
### 3. Network Segmentation ### 2. Device Health
- Isolate critical systems to prevent lateral attacks. - **Auto-patch** Windows, macOS, iOS via Intune or Jamf
- Encrypt all data in transit and at rest. - **Endpoint Detection**—CrowdStrike, SentinelOne, or the free Windows Defender if cash is tight
- **Certificate check**—only company-issued laptops get in
## Steps to Implement Zero Trust Security ### 3. Network Micro-Segmentation
- **Start small**—separate finance servers from marketing Wi-Fi
- **Use what you have**—VLANs, AWS Security Groups, Azure NSGs
- **Upgrade later** to fancy SDP tools like Zscaler or Cloudflare One
Transitioning to Zero Trust doesnt happen overnight. Follow these actionable steps: ### 4. Data Protection
- **Label & encrypt** your top 20 % of sensitive files (Microsoft Purview, free tier)
- **DLP rules**—block anyone from emailing credit-card spreadsheets to Gmail
- **BYOK** (Bring Your Own Key) if auditors start asking questions
1. **Assess Your Current Security Posture:** Identify vulnerabilities and gaps. ### 5. Continuous Monitoring
2. **Define Access Policies:** Establish strict rules for who can access what. - **SIEM**—free options: Wazuh, Elastic, or Microsoft Sentinel trial
3. **Deploy Zero Trust Technologies:** Invest in tools like identity-aware proxies and endpoint detection. - **SOAR playbooks**—auto-isolate a laptop that starts talking to Russia
4. **Train Employees:** Educate staff on security best practices. - **Quarterly policy tune-up**—apps change, threats evolve, so should you
5. **Monitor and Adapt:** Continuously refine policies based on threats.
## Challenges and How to Overcome Them ## The 90-Day Zero Trust Roadmap (Steal This)
While Zero Trust offers robust protection, businesses may face hurdles: ### Days 1-14: Discovery (The Awkward Truth Phase)
1. **Run Lansweeper or AssetTiger**—grab every laptop, phone, and forgotten server
2. **List your “crown jewels”**—customer DB, finance drive, that one Excel sheet with all the passwords
3. **Quick NIST 800-207 gap quiz**—Microsoft has a free 5-minute tool, thank me later
- **Complexity:** Start with a phased approach, focusing on high-risk areas first. ### Days 15-30: Identity Lockdown
- **User Resistance:** Communicate the benefits and provide training. - **Turn on MFA** for admins first, then roll to everyone
- **Cost:** Prioritize investments based on critical assets. - **Migrate top 5 SaaS apps** to SSO (Slack, Google Workspace, Zoom)
- **Create three roles**—Admin, Standard, Guest—done
## Conclusion ### Days 31-50: Device Hardening
- **Force auto-updates** via Intune or SimpleMDM
- **Install EDR**—even Windows Defender + cloud analytics works
- **Block jailbroken phones**—conditional access policies, two clicks in Azure
**Zero Trust Security: The Ultimate Guide for Businesses** highlights why this model is no longer optional—its a necessity. By adopting Zero Trust, organizations can better protect sensitive data, comply with regulations, and mitigate evolving cyber threats. ### Days 51-70: Network Segmentation Lite
- **Finance VLAN**—only finance PCs can talk to the ERP server
- **Test with RDP**—make sure HR cant accidentally open QuickBooks
- **Log everything** to your free SIEM
Start small, stay consistent, and remember: in cybersecurity, trust is a liability. ### Days 71-90: Monitor & Polish
- **Run a phishing test**—KnowBe4 or free Google tool
- **Create playbooks**—if laptop talks to bad IP, auto-isolate
- **Celebrate**—pizza budget: $200. Breach cost: $4.45 million (IBM). You just saved a fortune.
> *"The only secure network is the one thats never been attacked—until it has. Zero Trust ensures youre prepared."* — Cybersecurity Expert ## Budget Breakdown (Real Talk)
| Item | Small Biz (1-50 users) | Mid-Market (50-500) |
|---|---|---|
| **IAM** | JumpCloud $2/user | Okta $6/user |
| **EDR** | Windows Defender free | CrowdStrike $8/user |
| **ZTNA** | Cloudflare One free tier | Zscaler $12/user |
| **SIEM** | Wazuh open-source | Microsoft Sentinel pay-as-you-go |
**Typical 90-day spend for 100 users: $1,200-$3,000.** Compare that to **one** ransomware incident at $4.45 million. Easy math.
## Common Speed Bumps (And How to Hop Over Them)
- **“Users will revolt!”**
Run a 2-minute demo showing them passwordless sign-in with Windows Hello. Theyll ask for it.
- **“Legacy apps!”**
Use a simple identity-aware proxy (IAP) like Azure AD App Proxy. Zero code changes.
- **“No budget!”**
Start with Microsoft 365 E3 trial, layer on free Cloudflare tunnels. Upgrade later.
- **“Too complex!”**
Pilot with one department—say, accounting—then copy-paste the settings.
## Mini Case Study: 30-Person Design Agency in Austin
**Timeline**
- **Week 1**: AssetTiger found 47 devices and 3 forgotten AWS buckets
- **Week 2**: Rolled out Google Workspace SSO + free Google Authenticator MFA
- **Week 4**: Moved from VPN to Cloudflare ZTNA; support tickets dropped 35 %
- **Week 6**: Micro-segmented client design files with AWS S3 bucket policies
- **Week 8**: Passed SOC 2 audit two months early, landed a Fortune 500 client
Total spend: **$1,147** for three months. ROI? They sleep better and charge higher rates. **Priceless.**
## Quick-Start Checklist (Print & Pin)
- [ ] MFA enabled for every single account
- [ ] Top 5 cloud apps on single sign-on
- [ ] Auto-patching turned on for all laptops
- [ ] Finance and HR servers on separate VLANs
- [ ] One phishing simulation sent this quarter
- [ ] Incident response runbook tested once (even if its just you and Slack)
## FAQ Lightning Round
**Q: How long until I see benefits?**
A: Day 1 after MFA rollout. Seriously, youll wake up to zero fake-login alerts.
**Q: Does Zero Trust slow users down?**
A: Passwordless sign-in is actually faster than typing “FluffyBunny2024!” every morning.
**Q: What if I only have on-prem servers?**
A: Install Azure AD App Proxy or Cloudflare Tunnel. Takes 15 minutes, no firewall rules needed.
## Your Next 15 Minutes
1. **Download the free Microsoft Assessment Tool**—5 minutes
2. **Enable MFA on your own admin account**—3 minutes
3. **Schedule a 30-minute team huddle**—7 minutes to share this roadmap
Thats it. Youre already 10 % done.
> _"The best time to plant a tree was 20 years ago. The second best time is today."_ — Old proverb, still true for cybersecurity.
#ZeroTrustSecurity #90DayPlan #RemoteWorkSecurity

View File

@@ -7,7 +7,6 @@ import Head from '@/components/Head.astro'
import Navbar from '@/components/react/navbar' import Navbar from '@/components/react/navbar'
import { SITE } from '@/consts' import { SITE } from '@/consts'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import Posthog from '@/components/Posthog.astro'
const { const {
isWide = false isWide = false
@@ -19,11 +18,10 @@ const {
<Head> <Head>
<slot name="head" /> <slot name="head" />
<script is:inline src="https://analytics.ahrefs.com/analytics.js" data-key="+FHMgRP7/Duxaq5D0gZtJw" async></script> <script is:inline src="https://analytics.ahrefs.com/analytics.js" data-key="+FHMgRP7/Duxaq5D0gZtJw" async></script>
<link rel="sitemap" href="/sitemap.xml" />
<!-- Preload critical resources --> <!-- Preload critical resources -->
<link rel="preload" href="/fonts/GeistVF.woff2" as="font" type="font/woff2" crossorigin /> <link rel="preload" href="/fonts/ClashDisplay/ClashDisplay-Semibold.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/GeistMonoVF.woff2" as="font" type="font/woff2" crossorigin /> <link rel="preload" href="/fonts/Lexend/Lexend-Regular.woff2" as="font" type="font/woff2" crossorigin />
<!-- DNS prefetch for external resources --> <!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="//analytics.ahrefs.com" /> <link rel="dns-prefetch" href="//analytics.ahrefs.com" />
@@ -51,7 +49,6 @@ const {
})(); })();
</script> </script>
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/rss.xml" /> <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/rss.xml" />
<Posthog />
</Head> </Head>
<body> <body>
<div class="flex h-fit min-h-screen w-full flex-col gap-y-4 sm:gap-y-6 font-sans"> <div class="flex h-fit min-h-screen w-full flex-col gap-y-4 sm:gap-y-6 font-sans">
@@ -68,15 +65,5 @@ const {
</main> </main>
<Footer /> <Footer />
</div> </div>
<script is:inline async src="https://www.googletagmanager.com/gtag/js?id=G-QVK59XQK72"
></script>
<script is:inline>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-QVK59XQK72");
</script>
</body> </body>
</html> </html>

View File

@@ -7,10 +7,10 @@ import fs from 'fs'
import path from 'path' import path from 'path'
const MontserratRegular = fs.readFileSync( const MontserratRegular = fs.readFileSync(
path.resolve('./public/fonts/_montserrat_regular.ttf'), path.resolve('./public/fonts2/_montserrat_regular.ttf'),
) )
const MontserratBold = fs.readFileSync( const MontserratBold = fs.readFileSync(
path.resolve('./public/fonts/_montserrat_bold.ttf'), path.resolve('./public/fonts2/_montserrat_bold.ttf'),
) )
const dimensions = { const dimensions = {
@@ -151,7 +151,7 @@ export async function GET(context: APIContext) {
const pngData = image.asPng() const pngData = image.asPng()
return new Response(pngData, { return new Response(pngData as any, {
headers: { headers: {
'Content-Type': 'image/png', 'Content-Type': 'image/png',
'Content-Disposition': 'inline; filename="social-card.png"', 'Content-Disposition': 'inline; filename="social-card.png"',

View File

@@ -13,15 +13,14 @@ Disallow: /temp/
# Crawl delay for better server performance # Crawl delay for better server performance
Crawl-delay: 1 Crawl-delay: 1
# Sitemap location # Sitemap location (single canonical sitemap)
Sitemap: ${sitemapURL.href} Sitemap: ${sitemapURL.href}
Sitemap: ${new URL('sitemap-index.xml', SITE.href).href}
# Additional information # Additional information
# Host: ${SITE.href} # Host: ${SITE.href}
` `
export const GET: APIRoute = ({ site }) => { export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL('sitemap.xml', site) const sitemapURL = new URL('sitemap-index.xml', site)
return new Response(getRobotsTxt(sitemapURL)) return new Response(getRobotsTxt(sitemapURL))
} }

View File

@@ -1,87 +1,7 @@
import { SITE } from '@/consts' // Deprecated custom sitemap. Rely on @astrojs/sitemap integration to avoid duplicates.
import type { APIContext } from 'astro' export async function GET() {
import { getAllPosts, getAllProjects, getAllTags } from '@/lib/data-utils' return new Response('Moved to sitemap-index.xml', {
status: 301,
export async function GET(context: APIContext) { headers: { Location: '/sitemap-index.xml' }
try {
const posts = await getAllPosts()
const projects = await getAllProjects()
const tags = await getAllTags()
const site = context.site ?? SITE.href
const baseUrl = site.toString().endsWith('/') ? site.toString().slice(0, -1) : site.toString()
const staticPages = [
{
url: `${baseUrl}/`,
lastmod: new Date().toISOString(),
changefreq: 'daily',
priority: '1.0'
},
{
url: `${baseUrl}/projects/`,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '0.8'
},
{
url: `${baseUrl}/blog/`,
lastmod: posts.length > 0 ? posts[0].data.date.toISOString() : new Date().toISOString(),
changefreq: 'daily',
priority: '0.9'
},
{
url: `${baseUrl}/tags/`,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '0.6'
}
]
const blogPosts = posts.map(post => ({
url: `${baseUrl}/blog/${post.id}/`,
lastmod: post.data.date.toISOString(),
changefreq: 'monthly',
priority: '0.7'
}))
const projectPosts = projects.map(project => ({
url: `${baseUrl}/projects/${project.id}/`,
lastmod: (project.data.endDate ?? new Date()).toISOString(),
changefreq: 'monthly',
priority: '0.6'
}))
const tagUrls = Array.from(tags, ([tag, _]) => ({
url: `${baseUrl}/tags/${tag}/`,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '0.5'
}))
const allUrls = [...staticPages, ...projectPosts, ...blogPosts, ...tagUrls]
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
${allUrls
.map(
page => ` <url>
<loc>${page.url}</loc>
<lastmod>${page.lastmod}</lastmod>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>`
)
.join('\n')}
</urlset>`
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, max-age=3600'
}
}) })
} catch (error) {
console.error('Error generating sitemap:', error)
return new Response('Error generating sitemap', { status: 500 })
}
} }

View File

@@ -15,6 +15,7 @@ const currentUrl = Astro.url;
<PageHead slot="head" title="Tags" /> <PageHead slot="head" title="Tags" />
<Breadcrumbs items={[{ label: 'Tags', icon: 'lucide:tags' }]} /> <Breadcrumbs items={[{ label: 'Tags', icon: 'lucide:tags' }]} />
<h1 class="text-3xl font-bold mb-4">Tags</h1>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{ {

View File

@@ -3,34 +3,25 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@font-face { @font-face {
font-family: 'Geist'; font-family: 'ClashDisplay';
src: url('/fonts/GeistVF.woff2') format('woff2-variations'); src: url('/fonts/ClashDisplay/ClashDisplay-Semibold.woff2') format('woff2'),
font-weight: 100 900; url('/fonts/ClashDisplay/ClashDisplay-Semibold.woff') format('woff');
font-weight: bold;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'Geist Mono'; font-family: 'Lexend';
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations'); src: url('/fonts/Lexend/Lexend-Regular.woff2') format('woff2'),
font-weight: 100 900; url('/fonts/Lexend/Lexend-Regular.woff') format('woff');
font-weight: normal;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face {
font-family: 'ClashDisplay-Semibold';
src:
url('/fonts/ClashDisplay-Semibold.woff2') format('woff2'),
url('/fonts/ClashDisplay-Semibold.woff') format('woff'),
url('/fonts/ClashDisplay-Semibold.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: normal;
}
.font-custom { .font-custom {
font-family: 'ClashDisplay-Semibold'; font-family: 'ClashDisplay', sans-serif;
} }
:root { :root {
@@ -68,9 +59,7 @@
--radius: 0.5rem; --radius: 0.5rem;
--font-sans: --font-sans: "Lexend", sans-serif;
Geist, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono: --font-mono:
Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace; 'Liberation Mono', 'Courier New', monospace;