DDevineSecurity Engineer

Least-Privilege Dual-Control with Tamper-Evident Audit · Case study

Chokepoint

Live demo

Chokepoint addresses how to enable high-impact operations without granting excessive authority. The product provides role-based access control with four roles and default-deny enforcement, dual-control requiring distinct authorized approver with 15-minute expiry, SHA-256 hash-chained HMAC-signed tamper-evident ledger with integrity verification, explainable anomaly detection, policy simulation with What-If dry-run, and SIEM export with dedicated permissions. Built with Next.js App Router and TypeScript, tested with Vitest covering ledger tamper detection, RBAC and dual-control enforcement, and cryptographic operations. Includes Electron desktop build with security hardening, SBOM generation, and automated security scanning.

Built with

  • Next.js 16 App Router + TypeScript + Vitest
  • Cryptography: PBKDF2-SHA256, HMAC-SHA256, SHA-256 hash chaining
  • Tamper-evident ledger with integrity verification
  • Dual-control with distinct approver enforcement
  • Role-based access control with default-deny
  • Anomaly detection and risk scoring
  • Electron desktop with security hardening

Security concepts

  • Least Privilege
  • Dual-Control
  • Tamper-Evident Audit
  • Hash Chaining
  • HMAC Integrity
  • RBAC
  • Separation of Duties
  • OWASP ASI03

Watch the walkthrough

A narrated 1:47 walkthrough of the live demo — signing in as admin, the risk dashboard, tamper-evidence verification, anomaly feeds and the dual-control approval flow. Audio commentary included.

Open the live demo

01

Overview

Chokepoint is a full-stack security product built to answer one question: how do you let people — and increasingly, AI agents — perform high-impact actions without giving any single actor enough authority to abuse it?

It is a live, installable web application with real role-based authentication, a working two-person (dual-control) approval workflow, a SHA-256 hash-chained and HMAC-signed tamper-evident audit log, and explainable anomaly detection. It is framed around the OWASP Agentic AI failure class ASI03 — Identity & Privilege Abuse.

Where the substance is

The security reasoning lives in small, reviewed library modules (lib/); the UI is the proof it actually runs. A Vitest suite tests the security properties themselves, not just page rendering.

02

The problem

High-impact operations — wiping devices, changing access, moving money, reconfiguring production — share a dangerous property: a single account with enough privilege can cause irreversible damage, whether through malice, a stolen credential, or a compromised automation.

The problem gets harder with AI agents. Agents act fast, autonomously, and with borrowed human authority — exactly the conditions that make identity and privilege abuse so damaging. Chokepoint treats both humans and automated agents as named identities that must pass the same gates.

The design targets four goals simultaneously:

  • No single actor can complete a sensitive action alone (separation of duties).
  • Every action is attributable to an identity and cannot be silently erased (tamper-evident audit).
  • Every action is authorized against a least-privilege policy before it runs (default-deny).
  • Suspicious behavior is surfaced in human-readable form (explainable detection).

03

Threat model

The threat model assumes an attacker may hold a valid account (insider or stolen credential), may control an automated agent, and may attempt to alter the audit trail after acting. It treats the application's own authorization logic as the primary line of defense rather than trusting the network perimeter.

Assets

The ability to perform high-impact actions; the integrity and completeness of the audit record; session credentials.

Actors

Over-privileged users, stolen/reused credentials, malicious insiders, and compromised or over-permitted AI agents.

Abuse cases

Self-approval of destructive actions, role escalation, out-of-hours privilege use, brute force, and post-hoc log tampering.

Trust boundary

Anything reaching a route handler is untrusted until authenticated, authorized, and — for sensitive actions — approved.

04

Architecture

A Next.js (App Router) TypeScript application. Authentication issues a signed session cookie; a single policy engine authorizes every action; sensitive actions require dual-control approval; everything is written to a hash-chained, HMAC-signed ledger; and an anomaly layer scores risk for the dashboard.

REQUEST FLOW THROUGH THE CONTROL PLANEClientsweb · PWA · native (Capacitor path)Next.js App RouterTypeScript · UI + route handlersSigned sessionHttpOnly · SameSite=Strict · expiring cookiePolicy engine (lib/authz)RBAC matrix · can() · default-denyDual-control approvaldistinct + authorized approverTamper-evident ledger (lib/ledger)SHA-256 hash chain + HMACAnomaly detection (lib/anomaly)explainable risk scoringLive dashboardrisk index · severity · integrity status

Diagram reflects the module structure documented in the repository. Charts and diagrams are dependency-free so the strict CSP is untouched.

Key modules

ModuleResponsibility
lib/crypto.tsPBKDF2-SHA256 password hashing with per-user salt, HMAC-SHA256 signatures, SHA-256 hashing, and constant-time comparison.
lib/ledger.tsAppend-only, hash-chained, HMAC-signed event log plus verifyChain() that detects alteration, deletion, and reordering.
lib/authz.tsRBAC policy matrix, a single can() authorization gate on every action, and dual-control (distinct + authorized approver) enforcement.
lib/anomaly.tsExplainable risk scoring and severity classification for suspicious signals.
lib/session.tsSigned, HttpOnly, SameSite=Strict, expiring session cookie (Secure flag set when the connection is actually HTTPS).
lib/store.tsIn-memory store with seed/demo data and derived dashboard metrics (risk trend, severity breakdown, activity).

05

Security controls

Each control is implemented in code and exercised by tests. Click any stage in the interactive flow below to inspect the principle — or read the full control inventory.

Request path · control plane

01

Actor

Identity

A human operator or an automated/AI agent initiates a request. Every actor — person or machine — is a named identity with a role, because access control begins with knowing who is acting.

If this control failed

Anonymous or shared accounts make every later control un-auditable: you cannot enforce least privilege or attribute actions you cannot tie to an identity.

Interactive request flow — click a stage to inspect the security principle.

Least privilege & RBAC

Four roles — viewer, auditor, operator, admin — each with only the permissions they need. Every action passes through one policy gate.

Enforced: Policy matrix in lib/authz.ts; default-deny.

Separation of duties

A person can never approve their own privileged request. The approver must be a distinct, authorized identity.

Enforced: Dual-control checks in lib/authz.ts, covered by authorization tests.

Tamper-evident audit trail

Every event is SHA-256 hash-chained and HMAC-signed. Edit, delete, reorder, or re-sign with the wrong key and verification fails.

Enforced: lib/ledger.ts + verifyChain(); proven by ledger tests.

Credential hygiene

PBKDF2-SHA256 with a per-user salt and timing-safe comparison — no plaintext or fast hashes.

Enforced: lib/crypto.ts; crypto tests.

Session hygiene

Sessions live in an HttpOnly, SameSite=Strict, signed, expiring cookie. Secure is set behind real HTTPS so it also runs on a local preview.

Enforced: lib/session.ts.

Explainable anomaly detection

Failed logins, after-hours privilege use, unknown sources, privilege escalation, and automation are surfaced with human-readable reasons and severity.

Enforced: lib/anomaly.ts; anomaly tests assert signals fire on the right conditions.

Transport & app hardening

Strict Content Security Policy and security headers; dependency-free charts so the CSP stays tight and no new transitive risk is added.

Enforced: Response headers; npm audit reports 0 vulnerabilities.

Human and AI actors

Access control treats people and automated/AI agents as named identities, addressing identity & privilege abuse in agentic systems (OWASP ASI03).

Enforced: Actor model in the policy and ledger layers.

06

Auth & approval flows

Authentication

Credentials are verified against PBKDF2-SHA256 hashes with a per-user salt using timing-safe comparison. On success, a signed, HttpOnly, SameSite=Strict, expiring session cookie is issued. The Secure flag is set only when the connection is genuinely HTTPS (via x-forwarded-proto), so it is enforced behind TLS in production yet still works on a local HTTP preview.

AUTHENTICATION FLOWSubmit credentialsover HTTPS in productionVerify passwordPBKDF2-SHA256 + per-user salt · timing-safeIssue sessionsigned · HttpOnly · SameSite=Strict · expiringSubsequent requestssession cookie → policy engine

Authorization & dual-control

Every action passes through a single can() policy gate against a role matrix (viewer / auditor / operator / admin), defaulting to deny. High-impact actions additionally require approval from a second, distinct, authorized identity — the requester can never approve their own request.

DUAL-CONTROL APPROVAL FLOWActor requests sensitive actionrole + actionPolicy gate: can()?RBAC matrix · default-denyCreate mandateawaiting approvalSecond, distinct approverrequester ≠ approver · approver is authorizedExecute + append to ledgerhash-chained · HMAC-signed

07

Audit ledger & integrity

The ledger is append-only. Each entry stores a hash of the previous entry plus its own payload, forming a chain, and each entry is also signed with an HMAC keyed by a server secret. Verification recomputes the chain and signatures from start to finish.

// entry[i] integrity binding (conceptual)

entry[i].prevHash = SHA256(entry[i−1])

entry[i].hash    = SHA256(prevHash + payload + ts)

entry[i].hmac    = HMAC(key, hash)

// verifyChain(): recompute every link & signature

Tampering breaks verification in a detectable way:

  • Alter a payload → its hash no longer matches, and every later link fails.
  • Delete an entry → the chain has a gap; prevHash references break.
  • Reorder entries → prevHash links no longer line up.
  • Re-sign with the wrong key → HMAC verification fails (the server secret is unknown to the attacker).

08

Attack & failure scenarios

Rather than assert the controls work, the project reasons about how they fail and verifies the outcome. Selected scenarios:

An operator edits or deletes an audit event to cover an action

Attack · Tamper with a ledger entry's payload, remove an entry, or reorder events.

Outcome · verifyChain() recomputes hashes and HMACs and fails at the affected position — alteration, deletion, and reordering are all detectable.

Someone re-signs the log with a different key after tampering

Attack · Attempt to forge valid HMAC signatures without the server's secret.

Outcome · Without the HMAC key, forged signatures fail verification; the wrong-key case is explicitly tested.

A user approves their own destructive request

Attack · Requester tries to self-approve to satisfy the two-person rule alone.

Outcome · Authorization rejects it: approver must be distinct and authorized. Self-approval is blocked in code and in tests.

A lower-privileged role attempts an admin action

Attack · Viewer/operator calls an endpoint or action outside their role.

Outcome · The default-deny policy gate denies the action and records the attempt (an anomaly signal), rather than silently allowing it.

Credential guessing / brute force

Attack · Repeated failed logins, or timing-based user enumeration.

Outcome · Salted PBKDF2 hashing and constant-time comparison remove timing leaks; failed logins feed anomaly detection for risk scoring.

An automated agent over-uses privilege

Attack · An AI/automated identity performs high-impact actions or works outside expected patterns.

Outcome · Agents are named identities subject to the same RBAC and approval gates; automation and unusual patterns are flagged as anomaly signals.

09

Testing

The suite (npm test, Vitest — 26 tests across crypto, ledger, authz, and anomaly) tests the security properties rather than just rendering.

tests/ledger.test.ts

The audit chain detects altered payloads, deleted entries, reordered entries, and re-signed (wrong-key) entries.

tests/authz.test.ts

The policy matrix and dual-control rules — distinct-approver and authorized-approver — behave correctly.

tests/crypto.test.ts

PBKDF2 is salted and timing-safe, and HMAC keyed signatures verify as expected.

tests/anomaly.test.ts

Risk signals fire on exactly the intended conditions.

Dependency posture

The project reports npm audit 0 vulnerabilities, and dashboard charts are hand-built SVG to avoid adding dependencies that would loosen the Content Security Policy.

10

Results & takeaways

Chokepoint demonstrates that the controls are not just described — they run and they are verified: a policy gate that defaults to deny, an approval rule that blocks self-approval, and a ledger that provably detects tampering. It is installable as a PWA (with a Capacitor path to native) and deploys to Vercel, so a reviewer can be inside the console within a minute using a one-click demo account.

The engineering lesson that carried over from the earlier reset lab: security claims are only as strong as the tests that try to break them. Designing the controls and the failure cases together is what makes the difference between a demo and something defensible.

11

Limitations

Honest scope

This is a productized demonstration, not a hardened production platform. The limitations below are deliberate and documented in the repository's SECURITY.md.
  • Demo data and seeded accounts are held in an in-memory store; this is a productized demonstration, not a hardened multi-tenant production service.
  • Seed/demo credentials are intentionally simple so the app runs out of the box — production requires setting CHOKEPOINT_SECRET and CHOKEPOINT_SESSION_SECRET and issuing real credentials.
  • There is no production identity provider / SSO integration, no organizational directory sync, and no enterprise key management for HMAC keys in the demo build.
  • Anomaly detection uses explainable rule- and score-based signals, not a trained ML model; it is transparent rather than exhaustive.
  • Scale, high-availability, and operational concerns (backups, key rotation, distributed verification) are out of scope for the demonstration.

12

Future work

  • Persistent, durable storage for the ledger with externalized key management and key rotation.
  • SSO / OIDC integration and stronger MFA for production identities.
  • External, append-only log backends (e.g. WORM storage or a SIEM) and independent chain verification / anchoring.
  • Richer policy as code and configurable approval thresholds per action risk.
  • Hardened agent identity model for AI actors — scoped tokens, per-agent policies, and tighter rate/capability limits.