Open to client work · Columbus, OH

Full-stack developer AI-focused Based in Ohio

Backend security, AI-integrated products, and the unglamorous plumbing in between. Currently building for clients to fund the last stretch of a CSE degree at Ohio State

Hirad
Story

Tehran to Columbus, the long way

I was born in Iran in 1998 and moved to the US as a kid - grew up between both countries before settling in Ohio for high school. I studied Computer Science and Engineering at Ohio State, specializing in AI, after starting at Columbus State Community College

I'm three classes away from finishing my degree - two AI courses and a capstone - and I'm taking on client work right now to fund that last stretch myself, while building things I care about

I use AI tools daily. Every suggestion still gets tested - unit tests, real-Postgres concurrency tests, clean-install runs - before anything ships

🇮🇷 Tehran Columbus State CC Ohio State · CSE / AI
Selected work

Production systems, and the libraries extracted from them

Platform

AI Image Generation Platform

Live AI image-generation product: Gemini-powered generation and prompt help, Stripe payments, Google sign-in, Cloudflare R2 storage. The production app all three libraries below were extracted from

Gemini · Stripe · Cloudflare R2 · Google OAuth
Visit parsimage.app →
Platform

RTL Storefront Platform

Farsi (RTL) product site for a wholesale supplier: 8 server-rendered Astro pages with structured data, a sitemap, llms.txt, and GA4 phone-click tracking, plus scheduled security scans in CI

Astro · FastAPI · Postgres · 0 frontend tests
Visit amirpoulad.org →
Site

Farsi Restaurant Menu Site

Mobile-first Farsi (RTL) menu with a printable QR code: a swipeable dish carousel, a full-screen photo viewer, and a default breakfast or lunch tab that follows Tehran time. Static site on Cloudflare Workers

21 dishes · no build step · 5 security headers
Visit navid.hirad.dev →
Demo

ORNA - Bilingual Jewelry Demo

A full editorial-luxury jewelry shop concept: browsing, cart, wishlist, and checkout, in both Farsi and English with no page reload on toggle. One real feature underneath the demo shell: a live gold, silver, and USD/Toman price ticker with historical charts, backed by a real deployed API

Bilingual FA/EN · 37 ES modules · Live price API · 65 CI checks
Visit jewelry.hirad.dev →
App

Piano Trainer

Learn piano songs on your computer keyboard: notes fall toward an on-screen piano, Guitar Hero-style. Three modes, from watch-only to scored play. Runs fully in the browser, no account needed

8 songs · 3 modes · Tone.js · TypeScript
Visit piano.hirad.dev →
Library

secure-refresh-tokens

Refresh-token rotation for SQLAlchemy apps: a row lock serializes concurrent refreshes, and replaying a stolen token revokes the whole session family

0 tests · 8-way concurrency test on real Postgres in CI
View on GitHub →
Library

pg-rate-limiter

Rate limiter for FastAPI apps that already run Postgres, no Redis needed. Checks on several subjects (IP and user) batch into one all-or-nothing round trip

0 tests · 8-way concurrency test on real Postgres in CI
View on GitHub →
Library

cloudflare-gauth-proxy

Cloudflare Worker that caches Google's OIDC discovery document and signing keys, with a last-known-good fallback so a slow Google doesn't break sign-in

7 tests · born from a real sign-in incident
View on GitHub →
Infrastructure

Self-managed production VPS

A Linux VPS I run for production and staging apps: Docker Compose and nginx behind Cloudflare, Let's Encrypt TLS, GitHub Actions deploys, and a documented rebuild-from-scratch runbook

20-step rebuild runbook · CI/CD deploys
Open Source

Contributions merged into production codebases

12 merged pull requests across 4 external repos, including a security hardening fix in a clinic-management platform. Each row links to the merged PRs

Toolbox

What I build with

Full-stack

  • TypeScript
  • React
  • Astro
  • Python
  • FastAPI
  • Postgres
  • Farsi / RTL
  • Vitest + Playwright

Backend & security

  • Auth & tokens
  • Rate limiting
  • Row-level locking
  • Stripe payments
  • Abuse-case testing
  • Security scanning (ZAP, Semgrep, Nuclei)

AI-integrated products

  • Gemini image generation
  • LLM prompt assistance (Farsi + English)

Infra

  • Self-managed Linux VPS
  • Docker Compose
  • nginx
  • GitHub Actions CI/CD (self-hosted runner)
  • Cloudflare Workers
  • Cloudflare R2
  • Sentry

SEO & analytics

  • Structured data (JSON-LD)
  • Sitemap + IndexNow
  • llms.txt (AI-agent readability)
  • GA4 event tracking
Path

Timeline

Hirad, age 6
Age 6

One of many faces

A classroom full of kids

Hirad, age 7
Age 7

Room to run

Open space, no deadlines

Hirad, age 10
Age 10

Part of a team

Learning early that showing up counts

Hirad, age 23
Age 23 · OSU

Ohio State - CSE, AI specialization

Transferred in from Columbus State

Hirad, age 25
Age 25

Between meetings

Laptop open, coffee nearby

Hirad, age 26
Age 26

Still climbing

Some hikes are an excuse to think

Hirad, age 28
Age 28 · Now

Building for clients

Covering the last three classes myself

Client Work

How I can help

01

Full-stack builds

End-to-end web apps - auth, payments, data, deploy - shipped as one working system

02

AI feature integration

Adding AI features to an existing product - image generation and LLM-assisted flows through the Gemini API, with rate limits and metered usage around them

03

Backend hardening

Auth, rate limiting, and concurrency correctness for systems that face real abuse

04

Farsi / RTL and SEO-ready sites

Server-rendered RTL pages with structured data, a sitemap, llms.txt for AI agents, and GA4 phone-click tracking

Write-ups

Notes from the work

Six ways a nonce-based CSP breaks quietly

In plain English

Websites have a security rule that only lets "approved" code run on the page, like a bouncer checking IDs at a door, so hackers can't sneak in fake code that steals your information. The tricky part is that it's easy to accidentally lock out your OWN real code too, and the page can look completely fine while secretly being broken for every visitor. This write-up covers six ways that happens by accident, and how to catch it before real people do. Useful for anything that handles logins, payments, or personal information, basically any real website or app a business actually depends on

A nonce-based Content Security Policy is one of the strongest defenses against injected scripts, and one of the easiest to get subtly wrong. Each failure below is silent: the build passes, the unit tests pass, and the page still ships broken. I hit all six on real projects, then reproduced the browser behavior in Chrome to confirm it

1. A static page gets no nonce. A nonce must be fresh on every request, so the server has to render the page per request. Next.js documents that static pages can't get one. In one app, a simple form page fetched no data, so it built as static and every framework script on it was blocked. The page rendered and no button worked. Read the route table from a real build and check every page a first-time visitor can reach

2. A framework's own startup script has no nonce. Adding an Astro island with client:load to a nonce-only page passed the build, the type check, and every unit test. The island's inline bootstrap scripts carried no nonce, so a real browser would have blocked them and the island would never have woken up. We only saw it by reading the raw HTML of a real response. Our fix was pinning the scripts' SHA-256 hashes in the policy, which breaks whenever a version bump changes those bytes. Astro 6 now has a built-in hash-based option (a meta tag, not a header), so check that before pinning hashes by hand

3. A CDN optimizer rewrites your scripts. Cloudflare's Rocket Loader rewrites the type of every script tag and re-runs them through its own script. Cloudflare's documented CSP fix is to allow ajax.cloudflare.com in script-src. But 'strict-dynamic' makes browsers ignore host allowlists, so that fix does not apply to a nonce plus strict-dynamic policy. A batch of "Speed" recommendations took one of our sites from working to a half-loaded page within minutes: the HTML arrived and nothing ran. We turned Rocket Loader off. Treat any "auto-optimize" feature that rewrites markup or scripts as suspect. Plain compression and caching toggles are fine

4. Inline style= attributes are dropped. A nonce covers <style> and <link> elements, never a style attribute. Under a nonce-only style-src, every inline style prop is discarded. The page renders with browser defaults and nothing fails in the build. Move those rules into a real stylesheet

5. img-src 'self' blocks data: images. An inline SVG or any image delivered as a data: URI simply disappears. Search the app for every image source before writing the policy, including data: and blob:

6. Your test reads the nonce wrong. Browsers hide the nonce from the nonce attribute once the page is parsed, so getAttribute("nonce") returns an empty string. Read the .nonce property instead. A test that uses the attribute can compare an empty string to an empty string and pass with no policy at all

One check catches most of this. Build tools and jsdom never enforce a CSP, so load the page in a real browser and fail on any violation:

csp.spec.ts
await page.addInitScript(() => {
  window.__csp = [];
  document.addEventListener("securitypolicyviolation", (e) =>
    window.__csp.push(e.effectiveDirective + " blocked " + (e.blockedURI || "inline")));
});
await page.goto(url);
expect(await page.evaluate(() => window.__csp)).toEqual([]);

Tested, not just remembered: on a small test page in Chrome with a nonce plus strict-dynamic policy, a script without a nonce was blocked even though 'self' was listed, a script added by a nonced script still ran, an inline style attribute was ignored while a nonced style element applied, a data: image was blocked while a same-origin image loaded, and getAttribute("nonce") returned an empty string while .nonce held the value. I saw Rocket Loader on a live Cloudflare zone, not on the test page