Techottic

Security architecture

How it is built, and what it refuses.

Written for the person your procurement process forwards this to. Every claim below names a mechanism rather than an adjective, because your job is to find the gap. The last section is the gaps.

Identity and sessions

Sign-in is delegated to your directory
Microsoft Entra ID over OpenID Connect with PKCE (S256). The authorization code is exchanged server side, and the returned token is verified against Microsoft's published JWKS: signature, audience and expiry, plus a nonce bound to the request. The issuer is pinned as well when the deployment names one tenant, which is the configuration we recommend. Point it at a multi-tenant authority instead and the issuer check comes off, because those tokens carry the signing-in user's own tenant. The state parameter is compared with a timing-safe equality check rather than ===.
Sessions are opaque, not bearer claims
A session is a 256-bit random token stored server side. It carries no claims of its own, so a session cannot be edited into a more privileged one. The cookie is httpOnly, SameSite=Lax and Secure, which is why a script on the page cannot read it and why a cross-site form post cannot ride it.
One credential, and your directory holds it
In a deployment using your directory nobody needs a second credential, and removing somebody upstream removes their access here. Nowhere in the product is a password held in a form anything could read back.

Authorization, in three layers

A personal role
What the person is. Nine roles ship: employee, the three support tiers, two infrastructure teams, IT manager, administrator, and one reserved for an automated agent so software is never mistaken for a person. The vendor's own owner access is deliberately not one of them. It is a separate grant, held by an explicit record rather than by a role, precisely so that it cannot be reached by editing a group.
Roles granted by group
Membership of a group can grant a role for as long as the membership lasts. Resolved at request time, so removing somebody from the group removes the role immediately rather than at their next sign-in.
Roles granted inside one department
Somebody can be an agent in Finance and an ordinary employee everywhere else. This is the layer that makes a department a real boundary rather than a label.
The effective role is resolved server side, every time
The three are folded together on the server on every request, and the strongest of them is what that request runs as. Nothing about a role travels in a cookie, a form field or a token, so nothing about it can be edited by the person it applies to, and revoking any one of the three takes effect on the next request rather than at the next sign-in.

Where the department boundary is enforced

In the query, not over the answer
Lists, exports and search carry a department predicate inside the SQL that computes them. A filter applied to a result set is a filter somebody can forget; a predicate in the statement cannot be skipped by a code path that queried directly. Fetching one record by its id is the other shape: the row is read and its department judged before anything is returned. Both refuse. Only the first is structurally unable to leak through a count.
Fail closed, not fail open
A scope starts empty and is widened only by an explicit membership row. A table query that declares it is department-scoped and is handed no scope throws rather than returning everything. The failure mode is an error, not a disclosure.
Absent, not hidden
A record outside your departments answers 404, the same as a record that does not exist. Techottic does not render a page and then decide whether to show it, because that decision has to be right in every branch.
Navigation follows the same rule it enforces
Links are withheld when the page behind them would refuse, so a person is never invited somewhere they will be bounced from. The link is a courtesy; the server check is the control.

Writes

Every action re-checks, none trusts the interface
Every endpoint that changes data re-establishes who is calling and what they may do, rather than relying on the fact that the button was only rendered for some people. A hidden control is not a closed door, and an interface is not an authorization layer.
Ownership is re-read from the database
Actions that operate on a record re-read that record's department and requester server side. A submitted field cannot nominate whose record is being changed, or on whose behalf.
Statements are parameterized
Request input is bound, never concatenated. Nothing a person types, posts or puts in a URL reaches a statement as text. An identifier cannot be a bound parameter, so the few statements that have to name a table or a column take that name from a fixed internal list or from the database's own catalog, and never from a request.

Credentials and secrets at rest

Connected accounts are encrypted and bound to their owner
A technician's Microsoft refresh token is stored with AES-256-GCM, using that person's user id as additional authenticated data. Moving the row to another user makes it undecryptable rather than reusable. The key is held outside the database, so a copy of the file alone yields nothing.
Keys are identified, so rotation is detectable
Each ciphertext records a short fingerprint of the key that produced it. A row encrypted under a retired key reports that a reconnection is needed instead of failing as a decryption error.
API tokens are stored as hashes
A token is shown once at creation and then only its SHA-256 digest and an 11-character prefix are kept. The database cannot give a token back, to us or to anybody who takes a copy of it.

The public API

A token is a person
There is no anonymous service identity. A token acts as exactly one user and inherits that user's role and department scope, so the API cannot see more than the same person would see in a browser.
Rate limited by identity, not by address
Authenticated calls are bucketed per user, because behind a corporate NAT every employee shares one address and an address bucket would let one broken client throttle a whole site. Two tokens issued to the same person share that person's bucket: the limit belongs to the identity, not to the credential, so minting a second token does not buy more of it. Unauthenticated attempts are bucketed per address and locked out for a period once a small number of them fail. Exact thresholds on request.
Refusals are recorded
A rejected token writes an audit line distinguishing no credentials, a malformed header, and a well-formed token that is unknown, revoked or belongs to a disabled account. The presented token itself is never written, in any form. Most systems log successes, which is how an integration can fail for a week while the log stays quiet.

Inbound webhooks

Signatures are verified before the body is trusted
Zoom deliveries are checked against an HMAC-SHA256 signature over the raw body with a timing-safe comparison. Bot Framework activities are verified against Microsoft's published JWKS and the bot's own application id as audience.
An unconfigured endpoint is closed
A webhook with no secret configured answers 503 rather than accepting anything. There is no state in which the endpoint is reachable but unverified.

What is recorded

Actor, action, entity and detail
Sign-ins, permission changes, ticket movements, the report export, credential connections and disconnections, token creation and revocation, and refused writes. Each line records who acted, on what, and a human-readable detail. There is no previous-value column: a line says what a field was set to, and on a role change or an inline cell edit it does not say what it was before.
Readable by administrators, and honest about gaps
The trail is a table like any other, which means it can be read and, by somebody with database access, altered. It is evidence, not proof. If you need tamper-evident logging, say so during evaluation rather than after.
Not transactional with the change it describes
An audit line is written separately from the change it records, so the trail is evidence of what happened rather than a transactional guarantee that it was recorded. We are telling you because a page that implied otherwise would be wrong. If you need the change and its line to be atomic, raise it during evaluation rather than after.

Tenancy and data

One instance per client corporation
Techottic is single tenant. One client corporation per instance, with its own database, its own branding and its own directory connection. Separation between clients is not a column in a shared table, and no query in the product spans two of them. One exception, stated before you find it: our own commercial control plane, the records of who our clients are and what they are contracted for, sits in the same database as the product rather than in a separate one. It holds no client's operational data and nothing in it is reachable from a client's own surfaces, but it is not the clean separation the first sentence implies on its own, and moving it out is work we have not done.
No third-party analytics
This site and the product load no external analytics, no tag manager and no advertising script. No personal data about your people is sent to a third party as a side effect of using it.

What we do not claim

A security page that lists only strengths tells you the author was selling rather than describing. These are the things a careful reader would find, said first.

  • No SOC 2, ISO 27001 or equivalent certification is held today.
  • No independent penetration test has been commissioned yet.
  • No business associate agreement under the federal health privacy rules has been signed, and none will be. Health care is out of scope: a service desk supporting a clinical system sees patient data in ticket text as a matter of course, and the safeguards that regime expects are among the gaps on this list. We also hold no FedRAMP or StateRAMP authorization, no CJIS agreement and no payment card attestation. If your procurement needs one of those, we are the wrong supplier and would rather say so here than in month three.
  • The audit trail is not tamper-evident. See above.
  • The legal documents, including the data processing agreement, are drafted but await review by a solicitor and are not yet in force.
  • The datastore is durable and transactional, and it suits the scale we run at today. A deployment expecting unusually heavy concurrent write load should discuss that with us before committing.

Questions, or a security questionnaire to complete: contact@technooptics.com. Reports of a suspected vulnerability go to the same address, with vulnerability in the subject line. That is the general contact address and deliberately not the support intake, because a vulnerability report does not belong in the queue the whole service desk reads. It is not a dedicated security channel either: no acknowledgement is promised, no response time is committed, there is no triage process and there is no security team, so silence is not a decision. Good faith research that keeps to the disclosure terms carries a safe harbour: the provider will not bring or support a legal claim over it.