The Starting Point
Two months ago, we built Architect Linter to solve a real problem: teams'
codebases fall apart as they grow.
v5 used simple pattern matching for security analysis:
- Any function with "execute" in name → sink
- All parameters → potential sources
- Result: False positives everywhere
// Real code from a production NestJS app
// v5 would flag as CRITICAL VULNERABILITY
const executeWithErrorHandling = async (callback) => {
try {
return await callback();
} catch (e) {
logger.error(e);
return null;
}
};
const userInput = req.query.name;
const result = executeWithErrorHandling(async () => {
// Do something safe with userInput
return db.prepare("SELECT * FROM users WHERE name = ?").run(userInput);
});
// v5: 🚨 CRITICAL: "executeWithErrorHandling is a sink"
// 🚨 CRITICAL: "executeWithErrorHandling receives user input"
// Reality: ✅ Code is 100% safe (parameterized query)
Developers ignored all findings. Security analysis became useless.
The Rewrite: CFG-Based Analysis
For v6, we completely rewrote the security engine using Control Flow Graphs:
Step 1: Parse code into a CFG
req.query.id (SOURCE)
↓
const id = ...
↓
escape(id) (SANITIZER)
↓
db.query(id) (SINK)
↓
Result: ✅ SAFE (data was sanitized)
Step 2: Track actual data flow
- Which variables receive untrusted data?
- Where does that data go?
- Is it sanitized before reaching a sink?
Step 3: Only report real issues
// ✅ Safe: Data is parameterized
db.execute("SELECT * FROM users WHERE id = ?", [userId]);
// ⚠️ Unsafe: Direct interpolation
db.execute(`SELECT * FROM users WHERE id = ${userId}`);
// ✅ Safe: Data is escaped
db.execute(`SELECT * FROM users WHERE name = '${escape(userName)}'`);
Result: 95%+ Accuracy
| Metric | v5.0 | v6.0 |
|---|---|---|
| True Positives | 20% | 95% |
| False Positives | 80%+ | <5% |
| Developer Trust | ❌ None | ✅ High |
| Enterprise Ready | ❌ No | ✅ Yes |
Bonus: Zero-Config Setup
While we were at it, we also fixed the friction of "I have to configure
this for 30 minutes before I can use it":
$ architect init
🔍 Detecting frameworks...
✓ NextJS (from package.json)
✓ Django (from requirements.txt)
✨ Generating config...
Created: architect.json (90% auto-complete)
Ready to lint! Run: architect lint .
Now supports many modern frameworks (TypeScript, Python, PHP).
What This Teaches Us
-
Simple heuristics don't work for security
- "Contains 'execute'" is a bad signal
- Need to understand actual control flow
-
Zero-config adoption beats "perfect but complex"
- 30-minute setup → Users abandon
- 5-minute setup → Real usage
-
Focus beats breadth
- Supporting 11 languages poorly > supporting 3 languages well
- Dropped Go/Java, added Vue/Svelte (web-focused)
-
Tests catch everything
- We rewrote the core logic (risky!)
- 432+ tests meant we could refactor confidently
- Only broke 0 public APIs
Getting Started
cargo install architect-linter-pro
cd your-project
architect init
architect lint .
GitHub: https://github.com/sergiogswv/architect-linter-pro
Crates.io: https://crates.io/crates/architect-linter-pro
Docs: https://github.com/.../docs/MIGRATION_v6.md
What's Next
- v6.1: Variable tracking (catches injection in loops)
- v7: Pre-commit hooks + CI/CD templates
- v8: VS Code extension (if there's interest)
Questions? Hit me in the comments.
Top comments (0)