Accessibility & security
JD calls out: WCAG-compliant interfaces, OWASP best practices, XSS prevention, CSP awareness, safe API integration. Both topics are senior-level expectations that juniors often fumble. Showing fluency here separates you.
01Accessibility (a11y)
What is WCAG and what levels matter?
Web Content Accessibility Guidelines — the international standard. Three levels:
- A — minimum. If you fail this, the site is broken for many users.
- AA — the practical industry target. What "WCAG-compliant" usually means in a job description and what most regulations (ADA, EAA) effectively require.
- AAA — gold standard. Hard to hit fully across an app.
Current versions: 2.1 widely required, 2.2 published 2023 — adds focus visibility, target size, dragging alternatives. Some jurisdictions are starting to require 2.2.
The four principles (POUR): Perceivable, Operable, Understandable, Robust.
What are the basics every developer should get right?
- Semantic HTML. Buttons are
<button>, links are<a>, headings are real<h1>—<h6>in document order. - Labels for inputs. Every input has a
<label>oraria-label. Placeholder text is not a label. - Alt text for images. Decorative images get
alt="", content images describe the content. - Keyboard navigation. Tab order makes sense, focus is visible, no traps. Custom widgets need keyboard handlers.
- Color contrast. 4.5:1 for normal text, 3:1 for large text and UI components.
- Don't rely on color alone to convey meaning — pair with icons, text, or patterns.
- Form errors are programmatically associated with their fields and announced.
- Skip-to-content link at the top of the page for keyboard users.
If you ship a button that's actually a div with an onclick — you've created an inaccessible button, no matter how nicely it animates.
When do you reach for ARIA?
The first rule of ARIA: don't use ARIA. Use a real <button> instead of <div role="button" tabindex="0"> any day.
ARIA is for filling gaps where HTML doesn't have a native element — custom widgets like comboboxes, tabs, tree views. The patterns are documented in the WAI-ARIA Authoring Practices.
Common attributes:
aria-label/aria-labelledby— naming when there's no visible textaria-describedby— extra description (error message tied to input)aria-expanded/aria-controls— disclosure widgetsaria-live— announce dynamic changes (toasts, errors). Usepoliteby default;assertiveonly for genuinely urgent messages.aria-current— mark the current page in navrole="status"/role="alert"— for live regions
Bad ARIA is worse than no ARIA. Test with a screen reader if you're rolling something custom.
How do you make a complex data grid accessible?
This is directly relevant — AG Grid is the centerpiece of the Omnesoft frontend.
- Use the
gridrole pattern (or let AG Grid do it — it has built-in accessibility but you have to use it correctly). - Column headers connected to cells via proper roles.
- Keyboard navigation — arrow keys move between cells, Enter to edit, Escape to cancel. Standard AG Grid behavior; verify it's not broken by custom renderers.
- Focus management — when editing, focus enters the editor; when committing, focus returns to the cell.
- Custom cell renderers must be focusable and operable. A button-cell-renderer that's a div is a regression.
- Announce status changes — when a sort changes, an aria-live region should mention it.
- Headers should describe column purpose clearly — screen reader users can't see "Status" with a colored badge.
02Security (frontend angle)
What is XSS and how do you prevent it?
Cross-Site Scripting — attacker injects JavaScript into your page that runs in the user's session. Three types:
- Reflected — payload in a URL, reflected back in the page (e.g. unfiltered search-result rendering).
- Stored — payload saved on the server (a comment, a profile field) and rendered to other users.
- DOM-based — entirely client-side, payload manipulates the DOM via vulnerable JS.
Prevention:
- Don't insert untrusted strings as HTML. React already escapes everything you render via JSX — that's huge protection by default.
dangerouslySetInnerHTMLis the danger zone. Sanitize with DOMPurify if you must.- URLs in
href/srcneed protocol filtering — never allowjavascript:. - Content Security Policy (CSP) as defense-in-depth — even if XSS slips in, CSP blocks the inline script execution.
- Validate and sanitize on the backend too — never trust the frontend to be the only filter.
What is CSP and what does a basic policy look like?
Content Security Policy — an HTTP header (or meta tag) that tells the browser what sources of content are allowed. Mitigates XSS by blocking inline scripts, eval, and unauthorized origins.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
Practical points:
- Avoid
'unsafe-inline'for scripts — use nonces or hashes if you have inline scripts. frame-ancestors 'none'prevents your app from being framed (clickjacking protection).- Start in
Content-Security-Policy-Report-Onlymode and watch reports before enforcing. - CSP is hard to retrofit. Consider it during architecture, not after.
What is CSRF? Does it apply to your SPA?
Cross-Site Request Forgery — attacker tricks a logged-in user's browser into making a state-changing request to your API. Classic example: user is logged into bank.com, visits attacker.com, attacker.com submits a hidden form to bank.com that transfers money.
It applies if:
- You authenticate with cookies (the browser sends them automatically), AND
- Your API accepts state-changing requests without an additional token.
Defenses:
- SameSite cookies (
SameSite=LaxorStrict) — modern default, blocks most CSRF. - CSRF tokens — a per-session token that must be in the request header. Server rejects requests without it.
- Bearer tokens in headers (Authorization: Bearer ...) — sidesteps CSRF since browsers don't auto-send them.
If the SPA uses bearer tokens in headers, CSRF is largely a non-issue. If it uses cookies, set SameSite and use anti-CSRF tokens for mutations.
Where should auth tokens live in the frontend?
Trade-off, no perfect answer:
- HTTP-only secure cookies — JavaScript can't read them, so XSS can't steal them. But subject to CSRF concerns (mitigated with SameSite + tokens). Best for same-domain SPAs.
- localStorage — simple, works cross-domain. But any XSS reads it. Bad if your app has user-generated content or third-party scripts.
- In-memory — survives only the page lifetime. Most secure, but the user logs out on every reload unless you pair with a refresh-token flow.
- sessionStorage — same security profile as localStorage but cleared when the tab closes.
For an enterprise app like Omnesoft's, HTTP-only cookies with SameSite + CSRF tokens is the secure default if cross-domain isn't a concern. Worth probing what they do.
What's clickjacking?
Attacker iframes your page and overlays invisible UI to trick users into clicking. Defense: X-Frame-Options: DENY or the modern frame-ancestors 'none' CSP directive. For SaaS apps that should never be embedded, set this on every response.
What's prototype pollution?
An attack where untrusted input modifies Object.prototype, polluting every object in the runtime. Often happens via deep merge / clone functions that walk into __proto__ or constructor.prototype.
Prevention:
- Use
Object.create(null)for maps that take user keys. - Use
Mapinstead of objects when keys are untrusted. - Vet deep-merge libs — newer versions are usually patched.
- Don't accept arbitrary keys when validating input — use schema validation (zod) with strict shapes.
What about supply-chain risk in npm packages?
Real concern. Mitigations:
- Lockfiles — committed, audited, and reviewed when they change.
- Audit tools —
npm audit, Dependabot, Snyk, Socket. - Pin transitive deps when needed via
overrides/resolutions. - Vet new dependencies — number of maintainers, recent activity, alternatives. A two-line library that pulls 30 transitive deps is a bad trade.
- Subresource Integrity (SRI) for any scripts loaded from CDNs.
03OWASP Top 10 (frontend slice)
You don't need to memorize all 10, but know the ones that have a frontend angle:
| OWASP risk | Frontend angle |
|---|---|
| A01: Broken Access Control | Don't trust client-side route guards alone. Backend authorizes every request. |
| A02: Cryptographic Failures | HTTPS everywhere. Don't roll crypto in JS. |
| A03: Injection | XSS. Escape output. Sanitize anything you set as HTML. |
| A04: Insecure Design | Threat model. "What if a malicious user used this?" |
| A05: Security Misconfiguration | CSP, security headers, framework defaults. |
| A06: Vulnerable Components | Dependency hygiene. Audit. Update. |
| A07: Auth Failures | Token storage, session timeout, MFA flows. |
| A08: Software Integrity Failures | Build pipeline integrity, SRI on third-party scripts. |