PAUL MK

Projects

Three, and each one carries the artifact rather than a description of it. The architecture diagram is real, the diffs are the actual patches, and the rules run in CI.

The three projects, in summary

ParkSense

pre-pilot

A commercial parking platform: cameras read plates at the gate, an on-site Go agent runs the lane through network loss, drivers pay by M-Pesa on exit straight to the operator’s own till, and a barrier opens on a dry contact. Multi-tenant, with an operator console, an admin console, guard and driver apps, permits, enforcement, reconciliation and subscription billing. Roughly 36,000 lines, built solo.

UNTRUSTEDAPP ZONEDATA / OTagentFastAPItenant scopePostgresbarrier

Python · FastAPI · SQLAlchemy async · PostgreSQL/TimescaleDB · Go · React/TS · Docker

Kartly

48 tests

A regression harness for 24 web vulnerability classes. Each class is proven in both directions by code: an exploit test that expects the attack to succeed on main, and a fixed test that reuses the same attack code and expects it to be refused on the class’s fix branch. 48 tests in two suites.

■ 3 fix + rule■ 21 fix, rule pending

TypeScript · Node/Express · Prisma · Docker

Semgrep rules

4 rules

Custom JS/TS rules for unscoped lookups, mass assignment, path traversal and fail-open error handling, grouped by OWASP category with a test fixture sitting beside every rule.

const order = await db.order.findUnique({
where: { id },
const order = await db.order.findFirst({
where: { id, tenantId: ctx.tenantId },
})

rule: unscoped-lookup-by-user-id · a01-broken-access-control

YAML · Semgrep · JS fixtures · GitHub Actions

ParkSense — architecture and the decisions behind it

pre-pilot
Verified statechecked, not asserted · 2026-07-29
Verification state of the ParkSense codebase as of 29 July 2026
API tests486 passing, 0 failed
Migrations19, single head, round-trip verified on real TimescaleDB
API ↔ UI wiring72 endpoints wired, 0 phantom calls
Lintruff, 0 errors
Dashboardtsc -b, eslint and build clean
Edge agentGo build and vet clean
Mock data in the UInone — enforced, not aspirational

Every row is a claim about the code. None of them is a claim about the physical world — see the boundary below.

What it is

A commercial parking platform: cameras read plates at the gate, an on-site Go agent runs the lane through network loss, drivers pay by M-Pesa on exit straight to the operator’s own till, and a barrier opens on a dry contact. Multi-tenant, with an operator console, an admin console, guard and driver apps, permits, enforcement, reconciliation and subscription billing. Roughly 36,000 lines, built solo.

What is verified, and what is not. The recognition loop has been run end to end against a real camera — a phone over RTSP, reading real plates into real sessions. What has not happened is a gate: a mounted camera at gate height and angle, weather, moving vehicles, a boom barrier, and money. It has taken no payment and served no user.

Security decisions, and why

These are design choices with their reasoning, not findings. It is a commercial product, so nothing exploitable is published here. Thefull case study covers the five problems where the obvious implementation is wrong — network loss at the gate, probabilistic reads charging the wrong person, payment custody, and cross-tenant isolation.

Decision log6 entries
ParkSense security decisions and the reasoning behind each one
Money never touches the platformThe STK push goes from the driver straight to the operator’s own till, signed with the operator’s own Daraja credentials. That removes the payment-institution regulatory question entirely; revenue is subscription instead of a cut.
Exit always opens the barrierUnconditionally, including for a blocklisted plate. Refusing to let a vehicle leave is detaining a person, and a trapped driver costs more than any unpaid fee. The tradeoff is documented rather than hidden.
Low-confidence reads do not chargeBelow a confidence threshold, an exit calculates the fee but withholds the payment prompt and queues the session for human review. A bad entry read costs one session’s revenue; a bad exit read charges a stranger.
Reputation flags are never automaticThe cross-operator unpaid-exit counter is never incremented by code. A dirty lens at one site would otherwise defame a driver at every other site, so flagging is owner-only, manual, and requires a written reason.
Separation of duties on payoutsOperators cannot approve their own payouts. Those routes were deleted rather than re-guarded, because a guard is a thing someone can later loosen and a missing route is not.
Credentials are write-onlyPayment credentials are Fernet-encrypted at rest and returned to nobody — not operators, not support, not an owner. Only masked tails are ever read back.

Kartly — a regression harness, not a bug catalogue

48 tests · 24 classes

A commerce app carrying 24 bug classes on purpose, built so that every one of them is proven in both directions by code rather than by description. Each class has an exploit test that expects the attack to succeed on main, and a fixed test that reuses the same attack code and expects it to be refused on the class's fix/* branch — 48 tests in two suites. The grid says which classes are also closed by a Semgrep rule, which is the small number it is.

Bug-class coverage · 24 classes✓✓ fix + rule · 3 · ✓○ rule pending · 21
  • 01 · inject

    SQLi — UNION/data

    ✓ fix○ rule

  • 02 · inject

    SQLi — auth bypass

    ✓ fix○ rule

  • 03 · inject

    Blind SQLi

    ✓ fix○ rule

  • 04 · authn

    Broken auth

    ✓ fix○ rule

  • 05 · authz

    Broken access control (IDOR)

    ✓ fix✓ rule

  • 06 · authz

    Privilege escalation

    ✓ fix○ rule

  • 07 · authz

    Mass assignment

    ✓ fix✓ rule

  • 08 · xss

    Stored XSS

    ✓ fix○ rule

  • 09 · xss

    Reflected XSS

    ✓ fix○ rule

  • 10 · xss

    DOM XSS

    ✓ fix○ rule

  • 11 · flow

    CSRF

    ✓ fix○ rule

  • 12 · ssrf

    SSRF

    ✓ fix○ rule

  • 13 · file

    Unrestricted file upload

    ✓ fix○ rule

  • 14 · file

    Path traversal / LFI

    ✓ fix✓ rule

  • 15 · inject

    Command injection (RCE)

    ✓ fix○ rule

  • 16 · inject

    SSTI

    ✓ fix○ rule

  • 17 · parse

    XXE

    ✓ fix○ rule

  • 18 · flow

    Open redirect

    ✓ fix○ rule

  • 19 · authn

    JWT weaknesses

    ✓ fix○ rule

  • 20 · ops

    Security misconfiguration

    ✓ fix○ rule

  • 21 · ops

    Sensitive data exposure

    ✓ fix○ rule

  • 22 · logic

    Business logic flaw

    ✓ fix○ rule

  • 23 · ops

    CORS misconfiguration

    ✓ fix○ rule

  • 24 · logic

    No rate limiting

    ✓ fix○ rule

Semgrep rules — a finding is a snapshot, a rule is a ratchet

Fail-open error handling · OWASP A10fail-open-catch · js/ts
} catch (err) {
logger.warn('authz check failed', err);
return true; // control broke, request allowed
metrics.increment('authz.check_failed');
logger.error('authz check failed', err);
return false; // fail closed, and loud enough to page on
}

Denial on dependency failure, a metric incremented, and an alert on the failure rate. Failing closed silently is its own outage.

Why rule coverage is the small number4 built · ~24 possible

main is the vulnerable build; each class has a fix/* branch, so the diff between them is the lesson. Rule coverage is deliberately shown as the small number it is — Semgrep matches code shapes, and a good share of these classes do not have one. Business logic flaws, missing rate limiting and misconfiguration are real bugs with no pattern to match, and rounding the count up would overstate coverage on the one artifact meant to prove I do not overstate.