Secure by Design
TDR Technology Solutions · Updated 19 August 2026
1. Our commitment
TDR Technology Solutions commits to the principles of CISA’s Secure by Design programme, and — following CISA’s retirement of the K-12 education-technology pledge in August 2026 — has asked to sign the Secure by Design Pledge for enterprise software: seven goals, one year to demonstrate measurable progress on each, and public documentation of how. Section 10 states our position on each goal. The principles are unchanged:
- We take ownership of our customers’ security outcomes. A school district should not have to configure our product correctly to be safe. Where a security property matters, we build it into the architecture rather than exposing it as a setting.
- We embrace radical transparency and accountability. We publish this roadmap, our vulnerability disclosure policy, and the limits of what our software does. Where a protection is partial, we say so rather than rounding up.
- We lead from the top. Security ownership sits with the chief executive, not delegated to a function without authority.
2. Security leadership
| Role | Name | Accountability |
|---|---|---|
| Chief Executive Officer | Don Beeler | Overall accountability for product security; final authority on security-affecting design decisions and on vulnerability disclosure response. |
| Security contact | security@tdrtechnologysolutions.com | Intake for vulnerability reports under our published policy. |
Security accountability is not delegated. TDR is a small company and we describe that plainly rather than presenting a security organisation we do not have: one named executive owns security decisions, and that person is reachable.
3. Eliminating classes of vulnerability
The pledge asks specifically what we do to remove whole classes of defect rather than individual bugs.
Memory-safe languages by default. The e-mail product is written in Python and the voice product in C#, both memory-safe languages; the administrative consoles are TypeScript. This eliminates buffer overflows, use-after-free, and the other memory-corruption classes that account for a large share of exploitable vulnerabilities, and it aligns with the NSA/CISA guidance on memory-safe languages. Introducing C or C++ for performance would require documented justification and memory-safe coding standards; we have not done so.
Parameterised database access. We use an ORM with parameterised queries rather than string-built SQL, which removes SQL injection as a class. Every remaining string-built statement interpolates an internal constant — never input — and each such site is annotated with its justification and checked by the build gate described in §4, so a new one cannot appear unreviewed.
Escaped rendering in the consoles. The consoles render through a framework that escapes output by default. Every place that deliberately bypasses that escaping has been reviewed, annotated, and is checked by the build gate — a new raw-HTML sink fails the build until it is justified.
Tenant isolation enforced at the database, not the application. PostgreSQL row-level security enforces tenant separation beneath the application layer, so a missed check in application code cannot silently expose another district’s data. The isolation policies are generated from the data model itself, so a newly added tenant-scoped table receives its policy rather than depending on someone remembering.
Fail-closed handling of sensitive data. Where a control protects student information, failure stops the flow rather than letting it through.
Student data is removed by a dedicated de-identification component (the Student Privacy Engine) — field classification, then redaction — that every outbound path passes through, rather than by checks scattered through the application. It is explicitly fail-closed: if the de-identification model is unavailable, the content is not transmitted at all. There is no path in which data leaves because a safeguard was missing; the absence of the safeguard stops the transfer.
We state the limit as well: the model is statistical, so we design for its availability to be a hard gate and treat its accuracy as something to monitor rather than assume. Similarly, when a cryptographic timestamp cannot be obtained for an evidence record, the system records that the guarantee is degraded and surfaces it to administrators rather than proceeding silently.
Least privilege and need-to-know as code. Access to law-enforcement case material resolves through explicit membership and per-case grants; sensitive field classes are barred from the school-side environment by an allow-list rather than a filter. Release of unredacted material requires a named district administrator role and is never automatic.
Human review on consequential actions. No automated assessment takes a consequential action against a person without human confirmation. This is an architectural constraint, not a configuration default.
4. Secure development practice
Framework alignment. Our development practices are derived from NIST SP 800-218 (the Secure Software Development Framework), and product decisions are governed by the CISA/NSA/FBI Secure by Design principles. We say derived from deliberately: we have not undergone a third-party SSDF conformance assessment, and claiming formal alignment would imply one.
Automated verification on every change. Continuous integration blocks merges on: linting; a test-catalogue gate that fails if the catalogue is stale or a test is undescribed; documentation/code reconciliation that fails when documentation claims a component that does not exist; a component tracker requiring every built component to have mapped tests; a contracts lock; and full test execution on two Python versions. The suite currently stands at 2,430 automated tests in the e-mail product’s published catalogue plus 445 compiled tests in the voice product, and the counts are published rather than estimated.
A Secure by Design gate in the build itself. Since August 2026 the build runs our own Secure by Design checker: it scores the code against the seven pledge goals and fails the build on a high-severity finding. Concretely: every anonymous HTTP endpoint must carry a written justification or the build fails (today: every route is either authenticated or justified, each with its rate limit); a new dependency advisory without a published reachability disposition fails the build; a container definition with a shared default credential fails the build; and the report — including what the checker does not prove — ships with each release. The checker checks itself: it has its own tests, and its findings distinguish exact results from heuristic ones so nobody mistakes a pattern-match for a proof. Alongside it, dynamic scanning runs in the build as a blocking gate: every change to the cloud service boots the real API against a real database and runs an authenticated OWASP ZAP scan over its live API surface — and the build fails on any server error under fuzzing and on any missing security header. Findings we accept instead of fix are dismissed only with a written justification kept in the repository, and the full report ships as a build artefact.
Type checking. Static type analysis runs in CI (currently advisory, ratcheting toward blocking).
Software bill of materials. The voice product now generates a CycloneDX SBOM automatically on every build, with every package version pinned exactly — we removed all floating version ranges, because an SBOM over a floating restore names versions nobody can reproduce. The e-mail product publishes its direct-dependency SBOM with version, supplier and licence for each; the per-release transitive SBOM with hashes is the remaining gap and stays on the roadmap below. We state the distinction because “we publish an SBOM” and “we publish a complete transitive SBOM” are different claims.
Dependency and vulnerability management. We audit our dependency set against the public advisory databases on every change — pip-audit over every Python manifest we install from, and the NuGet vulnerability database over the voice product including transitive packages — and we act on what the audits return rather than filing them.
Our cadence: dependencies are audited on every change and at least monthly. A critical finding is remediated within 30 days, or — where the fix is not available to us because an upstream project has not released it, or another dependency constrains the version — the item is documented with the reason it remains open and a deadline, as §5 shows. We publish the cadence we actually hold rather than a shorter one we would miss.
5. Known vulnerabilities and how we handle them
CISA’s pledge asks for transparency, not perfection. A vendor reporting zero known vulnerabilities is either not looking or not saying — so when we do report a low number, we show our working.
As of 20 August 2026 our dependency audits report zero advisories across every manifest we install from, in both products. The one exception this page previously carried — a dispositioned denial-of-service advisory in the voice product’s media library — was closed by upgrade on 20 August 2026, ten weeks ahead of its published 31 October deadline; the row below records the episode as it was published.
How the eight advisories we published in August were closed
On 13 August we published eight distinct advisories with a reachability analysis: six could not reach our product as we use it, and two — a request.url reconstruction inconsistency that could have skipped authentication on the on-premise agent — we mitigated in our own code rather than waiting upstream. What kept all eight open was version coupling: our web framework capped the library carrying five of them below its fixed line, and an identity library capped our cryptography dependency below its fixed line.
Both caps have now lifted, and on 19 August we upgraded the web framework, its underlying toolkit, the identity library and the cryptography library together, verified the full automated suite on the new versions, and re-audited: zero advisories. The eight are closed by upgrade, not by disposition. The code-level mitigation for the request.url pair remains in place with its regression test — a guard we keep even though the underlying flaw is now also fixed upstream, because the test costs nothing and the pattern must never return.
One earlier upgrade attempt deserves a note, because we previously wrote here that a newer framework release “silently failed to mount any routes” and was rejected. The follow-up investigation found the truth was better and worse at once: the framework had made route registration lazy, the routes were served correctly — and it was our probe that read an internal structure instead of exercising the serving path. We corrected the probe to test what actually matters, verified the API serves every route, and took the upgrade. We mention it because “our test suite caught it” is only a virtue when paired with “and sometimes the test is what was wrong.”
What our own checker caught in the same pass
The build gate described in §4 found that our repository carried a second, independently pinned dependency manifest for the cloud service — and the production install script applied it after the corrected one, which would have silently reinstalled the pre-remediation versions we had already fixed. The manifests are now single-sourced, and the build fails if the same package is ever pinned differently in two places.
It has caught us a second time since. When the dynamic scan became a blocking gate (20 August 2026), its zero-server-errors rule exposed two defects our unit suites could not see: the fresh-install path built the database from one source and marked migrations complete from another, silently omitting eight tables on any new deployment; and one document library was never pinned in any manifest, so a clean install served errors where evidence packets should be. Both were fixed the same day, each with a regression test that makes the class of defect impossible to reintroduce. We publish these because they are exactly the class of failure a reviewer should ask about: the remediation you announce is only as good as the last install script that runs.
The one open advisory (voice product)
| Advisory | Reachability | Plan |
|---|---|---|
| Denial of service in a media-streaming library used by the call recorder (CVE-2026-54632): one malformed network packet can end an in-progress recording session. | Availability only — no confidentiality or integrity impact. The affected socket is reachable only from the school’s own network path, the recorder is supervised and restarts, and a dropped session re-establishes on the next call. | Closed 20 August 2026 — upgraded to the fixed release line ten weeks ahead of the 31 October 2026 deadline this page committed to. The disposition was removed the day the upgrade landed, exactly as published, and the build gate now runs with an empty exception list. |
If you run a scanner against our dependencies
Against the current manifests a Python advisory scanner reports nothing, and a NuGet scan of the voice product reports the single item above. If you find anything else, our vulnerability disclosure policy would genuinely like to hear about it — the audit cadence in §4 exists to catch new advisories between releases, and the build fails on any new advisory that lacks a published disposition.
How we got here
The audit that started this work reported 61 advisory matches across 8 dependencies — pins that had drifted roughly two years out of date. We remediated in stages, each verified by the full automated suite before merge: current versions, then replacing an unmaintained authentication library that was itself holding two further packages at vulnerable releases, then the code-level mitigation, and finally — once the version couplings lifted — the upgrades that closed the remainder outright. The published component inventory was regenerated at each stage so the versions we list are the versions we ship.
6. Secure by default
- No security-relevant setting is required of the customer to obtain the product’s core privacy property. Detection runs within the district by design; there is no configuration that redirects student data to us.
- Default deny. New access is granted explicitly and audited, rather than inherited.
- No default credentials. The product ships with no default password and no seeded account — and this is now enforced by construction, not convention: our container definitions refuse to start until instance-unique secrets are provided, the database application role takes its password from the environment, and the developer installer generates a per-machine password. The build gate fails on any shipped artefact that would start with a universally-shared credential.
- Single sign-on, in the baseline product, at no additional charge. Districts sign in with their own identity provider (Google Workspace, Microsoft Entra ID — standards-based OIDC). A district’s provider requires multi-factor authentication by default: a login that presents no second-factor evidence is refused unless the district has attested that its identity provider enforces MFA organisation-wide. Account linking happens only on a verified e-mail address, within the district’s own tenant, and every sign-in — success or refusal — is recorded with a named reason. This was the commitment we flagged as unshipped when we first published this page; it shipped in August 2026.
- Multi-factor authentication for local accounts — enforced for administrators. Local (non-SSO) administrator accounts now require multi-factor authentication: each administrator gets a 14-day enrolment window (with the deadline shown in the console), and after it a sign-in without MFA goes straight into enrolment — locked into enrolment, never out of the product. A colleague with user-management rights can reset a lost authenticator, which grants a fresh 48-hour window; every enrolment, refusal and reset is a named event in the audit log. Phishing-resistant passkeys (WebAuthn) shipped 20 August 2026: an administrator can enrol a passkey instead of — or alongside — an authenticator code and it counts as a full second factor; an administrator under the mandate cannot remove their last second factor, and the break-glass reset clears passkeys as well as authenticator secrets.
7. Transparency and accountability
- Vulnerability disclosure policy — published at https://tdrtechnologysolutions.com/security/: good-faith security research on our products is authorised, with a safe-harbour commitment, a named reporting channel, coordinated public disclosure after 90 days (earlier once a fix ships), and explicit guidance for researchers who encounter student or personal data. The machine-readable contact record is published at
/.well-known/security.txt(RFC 9116). - CVE policy. Every confirmed critical or high-impact vulnerability that requires customer action, or that is being exploited, receives a CVE record with accurate CWE and CPE fields — requested through MITRE as the vendor, issued with the fix and no later than 45 days after confirmation, with an advisory at a public URL and direct notice to every affected customer.
- Patch and support policy. We operate and patch the cloud service ourselves. The on-premise school agent ships with a nightly automatic security update — a release-channel image, signature-verified before restart, enabled by default with a documented opt-out and version freeze — and every on-premise component reports its version in its heartbeat, so both the district and TDR can see when something is behind. Supported release lines, patch cadence and a six-month end-of-life notice period are published.
- Logging and retention. The baseline product records identity events (every sign-in success and named refusal, token issuance, MFA evidence, source address), configuration and data-access actions in an append-only audit log, and law-enforcement record access — because viewing a record is itself a disclosure event under criminal-intelligence rules. Districts read their own logs in the console and through the API, retained twelve months online at no additional charge, with the full log set and retention schedule published.
- Evidence integrity. The original message is retained inside the district, fingerprinted with a SHA-256 hash, sealed with an RFC 3161 trusted timestamp from an independent timestamping authority, and held on immutability-flagged storage under a defined retention clock. Only the hash is transmitted for timestamping — the message itself does not leave the district.
We distinguish two properties deliberately, because they are often conflated: tamper-evidence (can alteration be proven?) is provided by the hash and the independent timestamp, and is cryptographic — it does not depend on trusting our storage, our administrators, or our company. Tamper-resistance (can alteration be prevented?) is provided by the storage flag, and is weaker: a sufficiently privileged operator can defeat it. We rely on the former and treat the latter as defence in depth, rather than claiming a write-once medium we do not require. If a deployment’s rules demand hardware-enforced write-once storage, the design accommodates it; it is not what the evidentiary guarantee rests on.
- Evidence certification for legal proceedings. Digital evidence self-authenticates under FRE 902(13)/(14) only when a qualified person certifies that the process produces an accurate result and that the copy matches by hash comparison. We identify in advance who at TDR can provide that certification and, if required, testify to how the system works — so the question is answered before a case needs it rather than during one. The individual is named to an agency on engagement rather than published here. This is offered as a billed litigation-support service, engaged only if a matter reaches proceedings. Our software also surfaces the FRE 902(11) requirement to serve the certification and give advance written notice to the opponent, because forgetting the notice is the common way self-authentication is lost.
- We publish our limits. Where a protection depends on a statistical model, or where a control is filesystem-level rather than hardware-enforced, we describe it accurately rather than using a stronger word.
8. How security enters the code
CISA asks signatories to describe their development and workforce practices. Ours are two, and we describe them at the size we actually are rather than as an organisation chart.
1. We design to NIST standards. Security requirements are taken from NIST guidance at design time rather than retrofitted after a feature works. The architectural decisions that carry the most security weight — processing student data inside the district, failing closed on redaction, enforcing tenant isolation at the database rather than in application code, requiring human confirmation before a consequential action — were design constraints, not later additions. This is what we mean when we say secure by design: the security property is a consequence of the structure, not a setting applied to it.
2. We audit the code itself, not only the design. Design intent and implementation are different things, so the implementation is reviewed for security in its own right, independently of whether it matches the design. The dependency audits and the build-time Secure by Design gate described in §4 are part of this, as is review of the code paths that handle student data, evidence and authentication. A published threat model — assets, trust boundaries, the threats at each boundary, and where in the code each mitigation lives — keeps that review anchored to the architecture rather than to habit.
Security review is a named responsibility held by the executive identified in §2, not a separate department. We would rather state that accurately than imply a security team we do not employ.
9. Roadmap — what we are improving next
Stating open work is the point of a roadmap; a roadmap with no gaps reads as marketing.
| Item | Current state | Target |
|---|---|---|
| Dependency posture | Zero advisories in both products (20 Aug 2026); the one dispositioned voice advisory closed by upgrade ten weeks ahead of its deadline (§5) | Keep it at zero by construction: the build fails on any new advisory without a published disposition |
| MFA for administrators | Enforced for local administrator accounts (14-day enrolment window, forced-enrolment path, break-glass reset); SSO districts MFA-by-provider | Done — passkeys shipped 20 Aug 2026; next: publish aggregate MFA adoption from the statistics endpoint |
| Automatic updates, on-premise | Shipped for the school agent — release images are now built and cryptographically signed on every release, and the appliance verifies the signature before restarting (fail closed, on by default) | The same channel for the voice connector and recorder |
| Media-library upgrade (voice) | Done — closed 20 Aug 2026, ten weeks ahead of the published 31 Oct deadline (§5) | Row retained so the deadline and its early close stay auditable |
| Evidence export | Identity events and audit log readable per district via console and API | A single export bundle — every event for a user or date range in one signed archive |
| External validation | Dynamic scanning (authenticated OWASP ZAP over the live API) runs in CI as a blocking gate — zero server errors under fuzz, security headers enforced, dismissals justified in-repo; static analysis, dependency audits and our own build gate on every change | An external penetration test — request for quotes drafted; stated here so the absence is not discovered, it is announced |
| Trusted timestamping | Shipped on the email path against an external authority | Self-hosted timestamp option for zero-egress environments |
| Static type checking | Advisory in CI | Blocking |
| SBOM | Per release, both products: voice CycloneDX with exact pins; e-mail full transitive CycloneDX generated from the resolved environment | Component hashes in the published SBOMs |
| Live-hardware validation | Test suite runs against simulated infrastructure | Validation against production-representative hardware |
10. The Secure by Design Pledge — seven goals, where we stand
CISA retired the K-12 education-technology pledge in August 2026 and pointed us at the enterprise Secure by Design Pledge: seven goals, one year to show measurable progress on each and to publish how. We asked to sign it. This is our starting position, goal by goal — what is built, what is open, and what we will publish. Our build-time checker scores the code against these goals and fails the build when it slips; its report ships with each release.
| Goal | Today (19 August 2026) | What we will publish within the year |
|---|---|---|
| 1 · Multi-factor authentication | Single sign-on with the district’s own identity provider (Google Workspace, Microsoft Entra — OIDC) is in the baseline product at no extra charge, and a district’s provider requires MFA by default: a login with no second-factor evidence is refused unless the district attests its IdP enforces MFA organisation-wide. Local administrator accounts have MFA enforced (14-day enrolment window, forced-enrolment path, break-glass reset); other local accounts have TOTP. | Aggregate MFA adoption — users by role and method, sign-ins by method, no names — from the statistics endpoint built for exactly this. Passkeys for local accounts shipped 20 August 2026. |
| 2 · Default passwords | Zero shipped artefacts start with a universally-shared credential; container definitions refuse to start until instance-unique secrets are set. | The count of products with default passwords (zero) and the build gate that keeps it there. |
| 3 · Reducing entire classes of vulnerability | All product code in memory-safe languages; parameterised database access with every exception annotated and gated; escaping-by-default rendering with every bypass reviewed and gated. | The CWE distribution of any CVE we issue; the gate results. |
| 4 · Security patches | We patch the cloud ourselves. Dependency advisories audited on every change across every manifest; the build fails on a new undispositioned one; the eight advisories published in August are closed outright by upgrade, the audit reports zero in both products, and the one voice advisory we had dispositioned with a 31 October deadline was closed ten weeks early. The school agent ships with a nightly automatic security update, on by default — release images are built and cryptographically signed on every release, and the appliance verifies before restarting (fail closed). Patch-and-support policy published. | Share of on-premise agents on a supported release and median time from advisory to adoption, from heartbeats; the same auto-update channel for the remaining on-premise components. |
| 5 · Vulnerability disclosure policy | Published at /security/ with /.well-known/security.txt: good-faith testing authorised, safe harbour, named channel, public disclosure after 90 days (earlier on fix). |
Lessons-learned notes from the reports we receive. |
| 6 · CVEs | CVE policy published: CWE and CPE fields on every record; issued for every critical/high-impact vulnerability needing customer action or under exploitation, with the fix and no later than 45 days after confirmation. | Every record, enriched; the annual count per product — a rising count while classes shrink is the expected shape, and we will say so. |
| 7 · Evidence of intrusions | Identity events with named refusal reasons, append-only audit log, law-enforcement access audit; districts read their own logs via console and API, retained twelve months online at no charge; log set and retention published. | The single evidence-export bundle per user and date range. |
What our checker does not prove. No external penetration test has run yet (our own authenticated dynamic scan now gates every build); pattern-based findings are leads, not proofs. We say so because a district’s security reviewer who runs their own scanner should find nothing we did not mention.