Day 98 of 100·Project Deep-Dive Day·1579 words

Popin Tech, part 2 — the security half of the internship, and the bridge to Zoth

Broken access control, RBAC granularity, time-bound privileged access, 20+ authorization tests, audit logging — the second resume framing of the same internship.

Know your own system

Your two resumes describe the same internship in two different vocabularies. The first says: "Reduced MTTD from hours to under 10 minutes via a centralized OpenTelemetry framework." The second says: "Conducted security reviews across 10+ RESTful API endpoints, identifying and remediating broken access-control, input-validation, and error-handling vulnerabilities in an event management platform serving 500+ users. Implemented granular role-based access control and time-bound privileged access workflows to minimize standing access, reinforced by 20+ authorization-focused integration tests and security audit logging across sensitive API operations."

Both are true. This article makes the security half as defensible as the observability half — and shows why it is the strongest possible on-ramp to talking about Zoth.

Broken access control is the number-one web risk for a reason

OWASP's A01 has been the top category for years, and the reason is structural: authentication is a gate you build once, authorization is a check you must remember everywhere. A missing WHERE tenant_id = ? or a missing if (resource.ownerId !== user.id) is invisible in code review, invisible in happy-path testing, and catastrophic in production.

The four failure shapes you should be able to name and spot:

  1. IDOR (insecure direct object reference) — the endpoint authenticates the user but never checks that the user may access this object. GET /events/123 returns someone else's event because the handler does findById(id) instead of findFirst({ id, ownerId }).
  2. Missing function-level authorization — the UI hides the "delete" button, but the API does not check the role. The client is not a security boundary; anything the frontend can call, an attacker can call.
  3. Privilege escalation by mass assignment — the update handler spreads the request body into the model, so a user sets role: "admin" on their own profile. The fix is an explicit allow-list of mutable fields, never a spread.
  4. Horizontal vs vertical confusion — horizontal is "same role, someone else's data"; vertical is "lower role doing a higher role's action". Different bugs, same family, and a test matrix must cover both.

Error handling is an authorization surface too

This is the subtle one, and it connects directly to something you have been thinking about since the early days of this curriculum: why some APIs return 401 and why some return 403, and why sometimes they return 404 when the answer is really 403.

  • 401 Unauthorized actually means unauthenticated — we do not know who you are. Missing or invalid credentials.
  • 403 Forbidden means we know who you are and you may not do this.
  • 404 Not found as a deliberate substitute for 403: if returning 403 confirms that a resource with that ID exists, you have leaked the existence of another tenant's object. For an ID space an attacker can enumerate, "not found" for both "does not exist" and "exists but not yours" is the correct answer.

And the third failure mode from your resume — verbose errors — is the same class of leak: stack traces, ORM error text, and database constraint names in a 500 response hand an attacker your schema, your library versions, and your file paths. The fix is a single error-handling middleware that logs the detail internally with a correlation ID and returns a generic shape externally.

This is exactly the "hide information from attackers" reasoning that shows up in Zoth: the difference is that in Zoth you implemented it as a platform property (a consistent error envelope, a tenant-scoped query layer that makes "not found" and "not yours" indistinguishable) rather than as a per-endpoint fix.

Granular RBAC, and where granularity actually lives

"Granular RBAC" sounds like a feature; it is really a data model decision. The three-question frame:

  • Who — the subject: user, service account, group.
  • What — the object class and the instance: event, and specifically event:123.
  • Which action — read, update, delete, and any domain verbs (approve, publish, export).

A role column on the user is coarse: it can answer "is this an admin" but not "may this person edit this event". As soon as you have resource ownership or per-resource sharing, you need either ownership checks plus role checks (simple, covers most cases) or a relationship-based model (ReBAC, the "Google Docs sharing" shape) where permissions derive from a graph. Most systems need the first; knowing when you have crossed into the second is the senior signal.

And the crucial discipline: every query is scoped, not every endpoint. If your data-access layer always requires a tenant/owner predicate, a forgotten check becomes a compile-time or test-time failure instead of a production breach. That single architectural choice is what Zoth's tenant scoping enforces (Day 40), and it is the most important thing to say when an interviewer asks "how do you make authorization not a game of whack-a-mole?"

Time-bound privileged access — this is Zoth in miniature

"Implemented time-bound privileged access workflows to minimize standing access" is the single most Zoth-adjacent line on either resume. Standing access is the default in most organizations and it is the thing Zero Trust and JIT access exist to kill:

  • Standing access: you are an admin forever. Every day is a day your credentials can be used or stolen.
  • Just-in-time access: you are not an admin. You request admin for a resource, for a bounded duration, for a stated reason; it is approved (possibly with MFA or dual approval); it is provisioned automatically; it expires on a timer; it is revoked.

That is a five-step lifecycle, and it maps one-to-one onto the six-state workflow you built in Zoth (Day 11): request → pending approval → approved → active → expired → revoked, with provisioning as an event-driven side effect rather than a synchronous call in the request path. When an interviewer hears the internship described as time-bound access and then hears Zoth described as a JIT access governance platform, the natural question is "so what did the platform add over the workflow you built at work?" — and the answer is the whole point of Zoth:

  • Scale of identity surface: one app's roles versus many integrations (GitHub, Notion, Azure) with an extensible connector architecture.
  • Policy evaluation as a first-class engine: deterministic RBAC guardrails plus an advisory risk layer, with the hard guarantee that the advisory layer can never bypass policy (Day 63).
  • Tamper-evident audit: hash-chained, append-only, so the log itself is evidence — not just "we wrote an audit row" (Day 39).
  • Zero standing privilege as an invariant, enforced by the state machine and the expiry ticker, not by discipline.

Say that comparison out loud in an interview. It converts two separate resume items into one narrative about a problem you have now solved twice, at two different scales, with the second time adding the things the first could not have.

The 20+ authorization tests — how to describe them properly

"20+ integration tests" is a number; the method is what earns the offer. The right description is a matrix, not a count:

  • Actors: anonymous, authenticated-but-unrelated user, resource owner, same-tenant different role, cross-tenant user, service account, expired-access holder.
  • Actions: read, list, create, update, delete, plus the sensitive verbs (approve, export).
  • Expected: allowed, 403, or 404-as-masked-403 — and which one, per case, is a deliberate decision.

The two tests people forget and interviewers love: the cross-tenant test (the single highest-value test in any multi-tenant system — it is the one that catches a missing scope predicate) and the "expired access" test (does the system actually stop working the instant the grant expires, or does it rely on the next request to notice?). And the meta-test: assert that the absence of a decision is a denial — default-deny, tested explicitly.

Audit logging across sensitive operations

The minimum viable audit record: who, what, on which object, when, from where, with what outcome, and under what authority (which grant, which policy decision). The properties that make it useful rather than decorative:

  • Append-only. No update, no delete, ever — enforced at the database-permission level, not by convention.
  • Written in the same transaction as the action (or via an outbox), so you can never have an action without its audit record. Traverse's derivation gap and Zoth's outbox are the same lesson from two directions (Day 60).
  • Tamper-evident — a hash chain, so alteration is detectable rather than merely forbidden (Day 39).
  • Complete enough for a recertification review — this is the part your resume hints at with "PCI-DSS/SOX access recertifications": auditors do not want logs, they want evidence that access was reviewed and is still justified. That is a query over your audit and grant history.

The line to end on

"I did security reviews on an application — endpoint by endpoint, finding broken access control, tightening error handling, adding granular RBAC and time-bound access with a real test matrix. Then I built a platform whose entire job is to make that category of bug structurally impossible: tenant-scoped data access, policy evaluated before provisioning, and a hash-chained audit log. The internship taught me what breaks; Zoth is my answer to it."

That is the strongest possible framing of two resume bullets, and it is completely true.

Bulletproof your resume

Say these answers aloud before moving on

These are the follow-ups an interviewer will actually ask about your projects.

01

You reviewed 10+ endpoints for broken access control. Walk me through your method.

For each endpoint I built the matrix: actors crossed with actions crossed with expected outcome. The actors were anonymous, an authenticated user unrelated to the resource, the resource owner, a same-tenant user with a different role, and a cross-tenant user. The actions were the full set the endpoint supported, including the destructive and sensitive ones. Then I checked four specific failure shapes: IDOR, where the handler fetches by ID without an ownership predicate; missing function-level authorization, where the UI hides an action the API still allows; mass assignment, where the update handler spreads the request body so a user can set their own role; and horizontal versus vertical privilege confusion. The highest-value test in that whole matrix is the cross-tenant one, because it is the only one that reliably catches a missing scope predicate.

02

Why would an API return 404 when the real answer is 403?

Because 403 confirms the resource exists. If IDs are enumerable and I return 403 for another tenant's object, I have turned my error codes into an existence oracle — an attacker can map my ID space without ever reading a record. Returning 404 for both does not exist and exists but is not yours makes the two indistinguishable. It is a deliberate trade-off: I lose a little debuggability for developers and gain non-disclosure. The related leak is verbose errors — stack traces, ORM messages, and constraint names in a 500 response hand over schema and library versions — so the fix is one error-handling middleware that logs detail internally with a correlation ID and returns a generic envelope externally.

03

What is the difference between the time-bound access you built at Popin and what Zoth does?

Same idea, different scale, and the platform adds three things the app-level version could not. Scale of identity surface: Popin was one application's roles, Zoth manages access across many systems through connectors with an extensible architecture. Policy as an engine: Popin had role checks; Zoth has deterministic RBAC guardrails plus an advisory risk layer with a hard guarantee that the advisory layer can never bypass policy. And evidence: Popin wrote audit rows; Zoth writes hash-chained, append-only audit records that are tamper-evident and queryable for recertification. The lifecycle itself — request, approve, provision, expire, revoke — is the same six-state machine in both, which is exactly why I could build the second one quickly.