Security
Each item uses a built-in ASP.NET Core, EF Core or Azure feature where one exists.
OWASP Top 10:2025
| Category | What Digest does |
|---|---|
| A01 Broken Access Control | Every page requires sign-in unless marked anonymous (fallback policy). Only admins can send invites. Every link query filters by the owner, and another user's link returns 404. /admin requires the Admin role, and admins must use an authenticator app. The summarizer only connects to public IP addresses (SSRF), checked on every connection including redirects. |
| A02 Security Misconfiguration | HTTPS redirection and HSTS. Content-Security-Policy limited to the site itself, nosniff, strict-origin-when-cross-origin, frame-ancestors 'none', no Server header. Detailed errors and health endpoints in Development only. |
| A03 Software Supply Chain Failures | Central package management with pinned versions. NuGet audit covers direct and transitive packages and fails the build on any known vulnerability. Vulnerable transitive packages are pinned to patched versions. Only approved third-party packages. |
| A04 Cryptographic Failures | Cookies are HttpOnly, Secure and SameSite=Lax with an 8-hour sliding lifetime. Data Protection keys are managed by Container Apps and shared across replicas. Secrets live in Key Vault and are read with managed identities. |
| A05 Injection | EF Core parameterized queries only. Razor HTML-encodes all output, including AI summaries and tags. Email templates HTML-encode inserted text. Antiforgery tokens on every form post. |
| A06 Insecure Design | Save, then publish to a queue, so a slow or failing page never blocks the user. Model output is validated against a fixed shape. |
| A07 Authentication Failures | Passwords of at least 12 characters, no forced complexity. Invite-only: accounts are created only from an admin's emailed invite, which is single-use, expires after 7 days and is stored as a SHA-256 hash. Lockout after 5 failures for 15 minutes, with an email notice. Optional authenticator-app 2FA, required for admins. Identical messages for known and unknown accounts on login and password reset. Rate limits of 5 per minute per client IP on account forms. |
| A08 Software or Data Integrity Failures | Row-version concurrency token on links. Idempotent message handling: a link that is already summarized is skipped. A link that still fails on the last delivery attempt is marked Failed before its message is dead-lettered. |
| A09 Security Logging and Alerting Failures | Structured security logs: sign-in success and failure, lockouts, password and 2FA changes, rate-limit rejections, admin changes. Accounts appear by id only. Passwords, tokens, links and email addresses are never logged. |
| A10 Mishandling of Exceptional Conditions | A global exception handler shows a generic error page. Status pages for 404 and 429. Permanent and transient summarizer failures are handled separately, and unprocessable messages are dead-lettered. |
OWASP Top 10 for LLM Applications
| Risk | What Digest does |
|---|---|
| LLM01 Prompt Injection | Page text is untrusted. It is sent between <page> delimiters, a closing delimiter inside the page is removed, and the instructions tell the model to ignore anything the page asks. |
| LLM02 Sensitive Information Disclosure | Prompts and responses are recorded in telemetry only in Development. |
| LLM05 Improper Output Handling | The reply must match the LinkSummary JSON shape: at most 3 bullets and 5 tags, with length limits. Anything else marks the link Failed. Output is HTML-encoded when shown. |
| LLM06 Excessive Agency | The model has no tools. It can only return text. |
| LLM10 Unbounded Consumption | 10 links per minute per user, page text truncated to 20,000 characters, a 400-token output cap, thinking turned off, and a spend limit on the Claude account. |
Admin panel
CoreAdmin's forms do not carry antiforgery tokens, so its pages are exempt from the token check. Every non-GET request to /admin must be same-origin instead, as the OWASP CSRF cheat sheet describes. A request is refused with 403 when its Sec-Fetch-Site is not same-origin or its Origin is another site.
SameSite=Lax cookies and required 2FA add to this. Every admin change is logged with the admin's account id.
Known limits
- Rate limits are counted per replica. A shared store such as Redis is the multi-replica upgrade.
- If publishing to Service Bus fails after the link is saved, the link stays
Pending. The outbox pattern closes that gap.