Back to Projects
Featured Project

Web Security Scanner

An automated web security scanner that checks HTTP security headers, SSL certificates, DMARC records, and exposed sensitive files. Returns a weighted score and grade from A to F with plain-language findings.

TypeScriptNode.jsExpressPostgreSQLDrizzleSecurityDockerOWASP
Web Security Scanner

What It Is

CIO Security Scanner is an automated web security assessment tool that checks any public website across multiple security dimensions and returns a scored, graded result with plain-language findings. It runs all checks simultaneously, saves every result to PostgreSQL, and exposes a clean REST API with two endpoints — one to run a scan, one to retrieve a stored result by ID.

It is live at webscan.christianoguine.be and the source is on GitHub.

Note: The live deployment has all security headers implemented and configured. You can verify this by running https://webscan.christianoguine.be through the scanner itself.

Why I Built It

Most websites have some form of security vulnerability. Even experienced developers may consider these issues negligible, often because their focus is on building features, not advanced web security. But from a security perspective, these small loopholes are exactly what attackers look for. You don’t need a sophisticated exploit when a misconfigured header or an exposed file is enough.

I built this scanner to make those risks visible. It scans any given URL, excluding private and internal IP ranges to prevent misuse and shows the kinds of weaknesses that usually require expensive enterprise tools or manual review to find. The goal is simple: catch the small things before an attacker does.

How I Built It

The scanner is a Node.js TypeScript REST API built with Express and PostgreSQL using Drizzle ORM. Every scan runs eight checks simultaneously using Promise.allSettled—a deliberate choice over Promise.all because individual check failures should never silence the others. A DNS timeout does not mean the SSL and header checks should return nothing. Every check settles independently and the results are collected from whatever completed.

// Run all checks in parallel and collect results
const findings: FindingResult[] = await Promise.allSettled([
  checkStrictTransportSecurity(),
  checkContentSecurityPolicy(),
  // ...other checks
]).then((results) => results.map((r) => r.value));

Each check is an isolated async function that returns a consistent FindingResult object: check name, status, severity, score contribution, and a plain-language message. This separation means each check can fail, timeout, or succeed without affecting anything else. The scanner collects them all, runs them through a scoring engine, and saves the complete result to PostgreSQL before returning the response.

// Example: Structure of FindingResult
const result: FindingResult = {
  check: "Strict-Transport-Security",
  status: "fail",
  severity: "high",
  score: 0,
  message:
    "Strict-Transport-Security header is missing. Add it to enforce HTTPS.",
};

The scoring model has two layers. Critical findings, exposed environment files, exposed git configuration, missing SSL, bypass score calculation entirely and force an automatic F. The reasoning is straightforward: no combination of correctly configured headers compensates for exposed database credentials. For everything else each passing check contributes weighted points toward a total of 100, mapping to grades A through F.

Because the scanner accepts user-supplied URLs and makes outbound HTTP requests, SSRF protection was a required design consideration from the start. Before any request is made the URL is validated using Zod — scheme checked, hostname extracted, and tested against every private IP range including RFC 1918 addresses, the AWS metadata endpoint, and IPv6 private ranges. The tool scans public infrastructure. It cannot be used as a proxy to reach internal systems.

The frontend is a single HTML page served as a static file by Express. The permission checkbox is required before scanning can begin — the scan button stays disabled until the user confirms they own the domain or have explicit authorisation to test it.

What It Checks

HTTP Security Headers — Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Server version disclosure, and X-Powered-By disclosure. Each header targets a specific attack class. Their presence or absence directly determines the browser-level protection available to your users.

SSL/TLS Certificate — direct TLS connection using Node's built-in tls module. No third-party library. Certificate validity, domain coverage, and days to expiry. Certificates within 30 days of expiry get a warning. Missing certificates get automatic F.

DMARC DNS Record — DNS lookup for the DMARC TXT record at _dmarc.hostname. Policy strength evaluated — p=none, p=quarantine, p=reject — because a DMARC record that only monitors provides no real protection against email spoofing.

Exposed Sensitive Files — requests /.env and /.git/config. Uses content pattern matching not just HTTP status codes to avoid false positives on servers that return 200 for all paths. Either finding is automatic F.

Stack

LayerTechnology
RuntimeNode.js 20 — TypeScript strict mode
FrameworkExpress 5
DatabasePostgreSQL 16 with Drizzle ORM
ValidationZod v4
MiddlewareHelmet, express-rate-limit
ContainerisationDocker Compose
DeploymentHetzner VPS, nginx, PM2, Let's Encrypt

Skills Demonstrated

  • Security Engineering: Designed checks around real attack vectors — XSS, SSL stripping, clickjacking, MIME confusion, email spoofing, credential exposure. Every check exists because of a specific, exploitable vulnerability class.
  • Backend Architecture: Runs all checks at once, but keeps going even if some fail; layered scoring model with critical failure bypass; Zod validation at the API boundary; clean separation between check functions, scoring engine, and persistence.
  • SSRF Awareness: Built SSRF protection into the design from the start — not as an afterthought. URL validation and private IP blocklist applied before any outbound request.
  • TypeScript Discipline: Strict mode throughout. Shared interfaces enforcing consistent structure across all check functions. No any types.
  • Production Deployment: Running live on real infrastructure with nginx, PM2, Let's Encrypt SSL, and Docker for the database. The scanner passes its own checks.
Related Projects

More projects

More systems and experiments connected to security, infrastructure, backend development, and AI.

Reusable Secure Auth System

Reusable Secure Auth System

A production-deployed authentication and authorization backend built under Secure SDLC principles. Supports hybrid authentication for browser and API clients, role-based access control, and a full security documentation suite.

Node.jsTypeScriptPostgreSQLExpressJWTRBACSecurity
View Project
RAG Portfolio Assistant

RAG Portfolio Assistant

An AI-powered assistant built into my portfolio that answers questions about my work, projects, background, and articles using a retrieval-based knowledge flow.

RAGLLMNuxtPostgreSQLpgvectorOpenAIAI UX
View Project
Security Monitoring with Wazuh SIEM

Security Monitoring with Wazuh SIEM

Open-source SIEM deployed on a production VPS with custom decoders, application-level detection rules, and MITRE ATT&CK threat classification. Real attack data from day one.

WazuhSIEMSecurityMITRE ATT&CKLinuxMonitoring
View Project