SafePlate — what I learned building a security platform
I spent six months building a TypeScript security platform. Here's what the architecture looks like, why I chose what I chose, and the mistakes I made along the way.
Building security tooling is weird. You're building for developers who don't want to think about security, but you need them to actually use your tool. It's a UX problem dressed up as an infrastructure problem.
SafePlate is a security platform that sits in your CI pipeline and scans for vulnerabilities, leaked secrets, and misconfigurations. TypeScript monorepo. Plugin architecture. Streaming-first design.
the architecture
User pushes → CI triggers → SafePlate CLI scans → Results stream to API → Dashboard renders
The key insight was streaming. Scanning a large monorepo can take minutes. Nobody wants to wait for a CLI to finish before seeing results. So every finding gets streamed to the API via a persistent connection as soon as it's discovered. You see the first results in under a second, even for huge codebases.
The scanner itself is modular:
scanner/
core/ — file walking, AST parsing, finding dedup
rules/ — each rule is a plugin (S3 bucket public? Hardcoded key? SQL injection?)
reporters/ — CLI output, JSON, SARIF, GitHub annotations
why TypeScript
Honestly? Shared types. The API and the CLI share the same type definitions. A Finding in the scanner is the same Finding the dashboard renders. Zero ambiguity about field names, shapes, or optionality. This alone saved us from so many "but it worked in the CLI" bugs.
Also, the plugin system is trivial to write in TypeScript. A new security rule is literally:
export default {
id: "no-hardcoded-keys",
scan(file: SourceFile) {
return findMatches(/AKIA[0-9A-Z]{16}/, file)
.map(m => ({ severity: "critical", message: "AWS key exposed", location: m }));
}
}
mistakes I made
Too many alerts. Our first version flagged everything. Password field in a config file? Flagged. Localhost URL? Flagged. Base64 string that looked like a key? Flagged. Developers ignored us within a week. We had to add a scoring system (severity × confidence × context) and only show the top hits by default.
Performance was an afterthought. The first scanner loaded every file into memory. Monorepos with 100k+ files would OOM. Rewrote it as a streaming parser that only keeps AST data for files that match rule patterns. Memory usage dropped 95%.
Git integration is essential. If you can't blame the PR author, nobody fixes findings. Commenting on PRs directly is 10x more effective than a Slack notification. We learned this the hard way.
current state
The platform is operational and being used internally. The codebase stays private for now, but the lessons are public domain. If you're building security tooling: stream results, reduce noise, and integrate with the tools developers already use.