How we test DeskGraph
This is the page you bookmark and send to your team. Not a sales pitch. Not a compliance checkbox list. How we actually verify the security of a multi-tenant system that processes your support data.
Four layers of defense
Every company says they take security seriously. The difference is whether you can show how it's enforced. DeskGraph runs four independent layers of security verification, on every commit, in CI.
Static analysis (SAST)
ESLint enforces security constraints at the linter level with 13 dedicated security rules. Timing attack patterns, unsafe regex construction, eval usage, dynamic require, insecure randomness, prototype pollution, and shell injection risks are caught before code reaches review. Math.random() is banned in favor of crypto. The delete operator is banned. SonarJS adds a second pass for correctness, complexity, and code safety. This runs in CI on every pull request. It's not a style guide. It's a security policy enforced at the linter level.
Type system as a security boundary
TypeScript strict mode with additional constraints: strict boolean expressions prevent type coercion in conditionals, unsafe type assertions are banned, unchecked indexed access is flagged at compile time. Drizzle ORM enforces parameterized queries at the type level. DELETE and UPDATE without WHERE clauses are lint errors. These eliminate entire categories of bugs before the code runs.
Integration tests against real infrastructure
Every test runs against PostgreSQL and Redis. Real foreign key constraints, real transaction semantics, real concurrency behavior. When the test suite passes, the actual system works, not a simulation of it.
Offensive security tests
20 dedicated test files that attack the system the way an adversary would. IDOR at every endpoint. SQL injection in ticket content, metadata keys, and JSONB fields. API key cache corruption and eviction. Bearer token injection with CRLF and null bytes. Rate limit IP spoofing. Error response information leakage. Prototype pollution. Auth boundary separation. Ghost organization defense. Cross-tenant vector search isolation. These run against real infrastructure on every build.
Most companies have one of these layers. DeskGraph runs all four, on every commit. The sections below describe what each layer catches.
Tenant isolation
The core promise of multi-tenant SaaS is that your data is yours. Not "mostly yours." Not "yours unless there's a bug." Yours.
Every data path is tested for cross-organization access. If Org A's API key can touch Org B's tickets, embeddings, feedback, search results, or knowledge base articles, the test suite catches it.
We go further than access control checks. Tenant isolation tests deliberately construct scenarios where one organization's data would match another organization's search query, then verify the isolation boundary holds. This is how we prove that vector search filtering actually works, not just that it's configured.
Mixed-object attacks are tested explicitly. A request with a valid ticket ID from your organization paired with a matching ID from another organization gets rejected. The boundary holds at the data level, not just the API level.
Injection and hostile input
Ticket content is user-supplied and adversarial by nature. Customers paste error logs, stack traces, HTML fragments, and sometimes content that looks exactly like SQL. The system handles all of it without flinching.
SQL injection is tested in ticket fields, not just API parameters. Subjects, bodies, resolution summaries. Anywhere user content flows through the system. Every piece of ticket content is treated as untrusted input, because it is.
Output encoding is tested across all transformation boundaries. Resolution summaries are verified for correct escaping of all injection vectors, including XML special characters and attribute injection attempts. If content passes through a boundary, the test suite verifies that boundary holds.
Authorization boundaries
IDOR testing covers every API endpoint that accepts resource identifiers. If you can guess or enumerate an ID, we've tested whether knowing it grants access it shouldn't.
Internal event types are rejected at external API boundaries. The API surface is not a passthrough to internal systems. If an event type only makes sense internally, the external API returns an error before processing begins.
API key caching is tested under corruption scenarios. What happens when cached authentication data is stale? What happens when key metadata diverges from the database? The test suite verifies that a compromised or malformed credential cannot escalate access, even when the cache is wrong.
Middleware ordering is tested as a security property. Authentication runs before authorization runs before business logic. This ordering could silently break during a refactor, so the test suite verifies it independently.
Rate limiting is tested for atomicity under concurrent requests. A burst of simultaneous requests cannot slip through the window by racing the counter.
State integrity
Ticket lifecycle transitions preserve data contracts. Reopening a closed ticket clears resolution provenance, because the old resolution no longer applies. Re-ingesting a resolved ticket without new agent messages preserves the existing summary, because nothing changed.
The test suite verifies that status changes never silently lose or corrupt data. If a transition should reset a field, it resets. If it should preserve a field, it preserves. Every lifecycle edge is tested, not just the happy path.
The no-mock policy
Tests run against a real PostgreSQL database and real Redis cache. Not in-memory fakes. Not mocked interfaces. The actual infrastructure the production system uses.
This is a deliberate tradeoff. Tests are slower. CI is heavier. But when the test suite passes, the actual system works, not a simulation of it.
We've seen too many projects where mocked tests pass while production breaks. In-memory database fakes don't enforce the same constraint behavior, don't have the same concurrency characteristics, and don't surface the same failure modes. We'd rather have slower tests that mean something.
What we haven't covered yet
Some failure modes are known but not yet tested. Certain unhappy paths in operational tooling, specific cache invalidation races under network partitions, and edge cases in bulk operations during partial infrastructure failures.
We're not listing these to pad the page. We're listing them because you should know. A vendor who claims complete coverage is either lying or hasn't looked closely enough. We'd rather tell you where the gaps are than pretend they don't exist.
These gaps are tracked and get closed as the system matures. If a specific scenario matters to your evaluation, ask us about it.
Need a DPA, have compliance questions, or want to talk through our architecture? [email protected]
For the overview of our infrastructure, encryption, and data handling, see the security page.
Ready to get on the list?
Join the waitlist for early access.
No spam. Notify on launch only. Privacy policy