Hardening the Modernized App
Last reviewed · content updated
IntermediateWhat you'll learn
~20 min- Audit a rebuilt application against the twelve-point governance checklist
- Derive authorization from server-side session state -- never from client-supplied identity
- Keep a decisions register so constraint-driven choices don't become later mysteries
Why the rebuild exists at all
Here is an honest observation from real replacement projects of FieldDesk’s shape: when you read their documentation afterward, the case against the low-code platform is rarely argued anywhere — it appears only as a one-clause premise. What IS documented, exhaustively, is where all the engineering effort went. And almost none of it went into ticketing features. It went into governance: authorization correctness, secrets handling, observability, deployment control, accessibility. The fair inference — label it as inference — is that this list is the reason such rebuilds happen: it is what low-code strains to deliver in a regulated environment.
That list is your hardening checklist for the rebuilt FieldDesk:
1. Authorization derived from server-side session 7. Rate limiting 2. Explicit API trust boundaries, central guards 8. Strict security headers 3. Schema-validated inputs at every boundary 9. Full audit trail 4. Versioned database migrations 10. Controlled file attachments 5. Vault-held secrets via workload identity 11. Accessibility to standard 6. Telemetry from day one 12. Three-layer test automationPrompt first
Audit this codebase against the following twelve controls. For each:PASS with evidence (file paths), or FAIL with the specific gap and aminimal fix. Pay special attention to: any route that determines theuser's role from request data rather than server session; any secretin config or env files; any endpoint missing input validation.
[paste checklist]Run this against any app — including things you built in earlier modules — and the findings cluster in the same three places every time. Those three deserve the rest of the lesson.
1. Identity comes from the session, never the request
The single most common vulnerability class in internal apps: the server trusts something the client sent about who they are — a role in a JWT the client can influence, a user ID in a query parameter, a hidden form field with an approver flag.
// WRONG: role arrives with the requestapp.post('/api/requests/:id/approve', (req) => { if (req.body.userRole === 'supervisor') { /* approve */ }});
// RIGHT: identity resolved server-side from the authenticated session;// authorization decided centrally, per route, from that identityapp.post('/api/requests/:id/approve', requireRole('supervisor'), (req) => { /* approve -- the guard already decided, from session, not body */});Two properties matter beyond the obvious: guards are centralized (one requireRole implementation, so a fix fixes everything, and Lesson 4.2’s matrix can enumerate what each role reaches) and every route has one — the global definition of done from Lesson 1.4 said “no route without an authorization check” precisely so a forgotten guard is a lint failure, not a pentest finding.
2. Secrets: the app never holds them
Lesson 2.3 wired one connection string through Key Vault + managed identity. Hardening generalizes it into a posture: the application’s configuration contains references, never values. No secret in code, config files, or pipeline variables; the runtime identity resolves references at startup; rotation happens in the vault without redeploying. During promotion (Lesson 4.3) the runbook will explicitly verify the identity can resolve its references in the target environment before traffic arrives — the classic promotion failure is an app whose identity has no vault access in the environment it just landed in.
3. The decisions register
The least glamorous artifact on the checklist, and the one your successors will thank you for. Real rebuilds accumulate constraint-driven choices: sessions in a server-side store because the platform’s sticky routing can’t be guaranteed; uploads proxied through the API because direct-to-storage was blocked by tenant policy; SLA timers computed in the domain layer, not the database, because regional clock policy differs. Each is reasonable — and each looks arbitrary or wrong to a maintainer two years later unless the constraint travels with the choice.
The register is a flat file in the repo — decision, constraint that forced it, alternatives rejected, revisit-when trigger. You have already seen the pattern twice: Lesson 2.4’s “Oracle on AKS; revisit if a managed offering appears” and Lesson 3.3’s sidecar-manifest exception. Hardening makes it a standing habit. One line of prompt keeps it honest: “list every non-obvious technical choice in this diff; for each, draft its register entry.”
The roadmap in Lesson 1.4 put security remediation first for the legacy estate. The same logic applies to the rebuild: retrofitting central authorization onto forty routes is a slog; wiring it into the first three routes is an afternoon. The guardrails-first task ordering exists exactly for controls 1-3 and 5 — they are cheap on day one and expensive on day three hundred.
A code review finds that the mobile client sends the technician's crew ID in each request body, and the API uses it to scope which work orders are returned. The client 'always sends it correctly.' What does control #1 say?
Key takeaway
The twelve-point checklist is the real reason rebuilds like FieldDesk’s happen — run it as an AI-assisted audit on everything you ship. Identity from the session through central guards, secrets as references resolved by workload identity, and a decisions register so constraints outlive the people who hit them. Next: proving all of it works, systematically.