emas.ai

Trust & Security

AuditCore is built on the same compliance framework it audits. This page documents our security posture, data handling practices, and regulatory compliance — so you can evaluate us the same way we evaluate your systems.

96 / 100
Our own audit, published unedited
AuditCore self-audit — 4 August 2026
We run our scanner on our own codebase across five core profiles and publish the result whether or not it flatters us: 93 pass, 0 block, 9 warn across 102 checks — score 96, grade A. On 2 August this page read 84, grade B, across 91 checks. The code did not get worse; we added 20 more checks that inspect code and configuration rather than documents, and seven of them found controls we describe in writing and have not built. Read the reason before the grade. Only one of those seven was a control we lacked: data subject rights, which we built on 4 August, the day after the check found it missing. Four of the other six were faults in our own checks — they were reporting gaps in controls we already had. Our network segmentation check could not match the ordinary Docker Compose form. Our model card check ignored the folder model cards live in. Our access control check did not recognise the authorisation model of the web framework we are written in. And our cookie consent check demanded a consent banner from a site that sets no tracking cookie. Every one of those faults was reaching customers too, not just us. The grade moved because the measurement got more accurate, not because the code got safer — the same reason it moved down two days ago, running the other way. This is the second time deepening our own measurement has cost us a grade, and the trade is the right one: a score that only moves when the code improves is worth more than one that flatters the scanner. On 18 August it moved again, 82 to 76, and this time we did it to ourselves on purpose. We built a corpus of deliberately planted credentials and NRIC values — the samples our detection checks must fire on — and our own scanner duly found them. Five of the eleven warnings are that test data. We could have excluded the directory and kept the higher number; we downgrade it to a warning instead, because a planted NRIC is still an NRIC sitting in the repository, and a scanner that cannot see its own fixtures cannot be trusted to see yours. Later the same day it went back to 82, and that time the code really did get better. Two of the warnings were controls we genuinely did not have. We log every AI inference this product makes — the prompt digest, the answer, and the decision it drove — into a hash-chained log, because NAIO 2026 requires AI output to be reviewable afterwards and we were asking customers for that while keeping nothing ourselves. And our application logs now ship to a collector that runs beside the app with no credentials and no published port, so an attacker who reaches the API cannot erase the record of reaching it; the capability had existed since 2 August and was switched off, because there was nothing on the other end. Two more were faults in our own checks, both of which had been reaching customers: our AI-logging check knew the name model_output_log and not ai_output_log, and our HTTPS check could not tell a router at 10.0.0.1 from the open internet. It also found one real thing we would rather it had not: our timestamp authority spoke plain HTTP, which meant the digest of a customer's assessment crossed the network in clear. We moved to a TSA that offers TLS. The nine warnings that remain are all low confidence, and eight of them are our own test fixtures and rule definitions — the scanner seeing the planted evidence it is supposed to see. On 19 August we changed a scoring rule, and it moved our own grade from B to A. You should read that sentence sceptically, so here is everything you need to. The findings did not change: the same 93 passes, the same nine warnings, the same nine files. What changed is that a low-confidence warning now costs one point instead of two. Every detection check already weighs its evidence and names the reason — a credential inside a rule definition is reported as a rule definition, not as a leak — and the score was ignoring all of it, charging a finding we had ourselves labelled 'this is your own test fixture' at the same rate as a real one. The report and the number were arguing, and only the report was right. Half rather than nothing, because a planted NRIC is still an NRIC sitting in a repository. A BLOCK is not discounted at all, at any confidence: if we are not sure enough to charge full price it should have been weighed down to a warning first, and deployment must stop, probably is not a finding. Under the previous weighting this same scan was 82, grade B; both numbers are printed here on purpose. The change applies to every customer's report identically, and it is the reason a codebase with a serious test suite is no longer punished for having one. It went unnoticed for eighteen months because there was no test on the scoring function at all — there is now, and it asserts this page's figure, so if the two ever drift apart a test fails rather than a claim quietly goes stale. Then we asked the obvious follow-up question, and the answer was worse than the original problem. If we score ourselves an A, can anything else? We scanned eighteen real codebases to find out — our own nine products, and nine of the most widely deployed Python packages there are, including fastapi, requests, sqlalchemy and pydantic. Seventeen of the eighteen scored exactly zero and grade F. The only exception was AuditCore. Five BLOCK findings reached zero under the old arithmetic and every real codebase has more than five, so the scale had no resolution at all — it could not tell eight failures from eighteen. That is not a grading scale, it is a test of whether a project is AuditCore, and no adjustment of the band boundaries could have fixed it because there was nothing between 0 and 91 to band. The score is now the proportion of checks passed, which spans 27% to 96% across that set, with severity setting a ceiling the proportion cannot lift: any BLOCK caps the grade at C, five or more at D, because passing 95% of checks while missing a statutory requirement is not an A. Our own figure moved from 91 to 96 and stayed grade A; the codebases that used to be indistinguishable at zero now land between F and C, in an order you can argue with. The full data, the method, and four limits we could not resolve — including that we are the only project in the set that had configured the scanner, and so the only one whose findings were weighed down — are published in docs/CALIBRATION_20260819.md.

Self-Audit Results — 102 Checks (4 August 2026)

Every check below ran against this product’s source code on the date shown. We publish the failures too — a compliance vendor that only shows you its passes is showing you a brochure, not an audit. Check counts rose in August because each framework gained implementation-level companions: checks that read code and configuration rather than documents.

FrameworkChecksOur resultKey obligations covered
PDPA Malaysia 20244441 pass · 3 warnProcessor liability (s.9), 72-hr notification (s.12B), data minimisation, DPA, cross-border transfers, data subject rights, PII pattern detection, granular consent schema, residency declaration
NAIO AI Governance 20262321 pass · 2 warnAI governance disclosure, explainability mechanism, risk classification, hallucination logging, bias assessment, model card, AI incident reporting, decision log, human review route
Cyber Security Act 8542726 pass · 1 warnIncident response plan, SIEM monitoring, BCP/DRP, NACSA reporting, VAPT schedule, RBAC, supply chain assessment, backup procedure, CISO designated, tamper-evident audit trail
OWASP Top 1041 pass · 3 warnParameterised queries, no hardcoded secrets, HTTPS enforced, pinned dependencies
CIS Controls v844 / 4 passMFA policy documented, MFA implemented in code, patch management documented

What Our Own Audit Flagged — and What We Did

Full disclosure of every non-passing finding from the run above.

FindingOur assessmentStatus
Personal data in an application log
PDPA-PII-04
Genuine — now fixed. Seven lines of a local diagnostic log contained customer email addresses. The file was local to the host and excluded from version control, so nothing was published or transmitted — but writing personal data to logs is a data-minimisation failure regardless. We now redact personal data at the output boundary, so every log line from every service is filtered before it is written. The historical entries were redacted in place, and logs are purged on a retention cycle.Fixed — verified 23 July 2026
SQL injection risk
OWASP-A03-B01
False positive — check rebuilt. The old check flagged any formatted string inside a database call. It now parses the code and separates an interpolated value (injectable) from an interpolated identifier such as a column name, which SQL cannot parameterise. Our four sites are all identifiers, so they are reported as a warning for review rather than a block. Real injections still block — we keep regression tests covering five different injection forms.Fixed — verified 23 July 2026
Identity-number values in source
PDPA-PII-01
False positive. The matches are the example patterns inside our own detection rules and their documentation — the scanner recognising its own rulebook.Fixed — verified 23 July 2026
Prohibited AI practice indicators
NAIO-B03
False positive. Same cause: the terms this check searches for are listed in the check’s own definition and in our product documentation.Fixed — verified 23 July 2026
Unpinned dependency versions
OWASP-A06-W02
Genuine — now fixed. Twelve of thirteen dependencies were specified as minimum versions rather than exact ones, while this page claimed otherwise. They are pinned to the versions the images were built and tested against.Fixed — verified 1 August 2026
Ten documented controls not implemented
implementation companions
Genuine — all ten now built. Our own implementation checks found that we documented controls we had not built. Shipped on 2 August 2026: a hash-chained audit trail where editing any past entry breaks every link after it, per-purpose consent that pins the version of the terms in force when it was given, a machine-readable data-residency declaration, and a human-review route that can uphold or overturn any automated decision. Closed 18 August 2026: log forwarding was built but shipped disabled, because there was no collector to ship to and our check rightly refuses to credit a capability nobody switched on. We built the collector — a process beside the app with no credentials, no published port, and a log directory the application containers cannot mount, so reaching the API no longer means being able to erase the evidence of it. Forwarding also had to be widened to carry what this codebase actually writes: it was attached to the logging framework, and almost everything here is print(), so the control had been shipping a fraction of the record it claimed to preserve.All ten — nine verified 2 August 2026, the last 18 August 2026
Seven gaps found by round two — one real, four check faults, two closed
PDPA-2024-W14/W15/W16, ACT854-W17/W18/W20, NAIO-W11
One control built; four of our own checks were wrong. Genuine, and fixed: data subject rights. You can now download everything we hold about you as a machine-readable file, correct your email, or erase your account — a company selling PDPA compliance had been holding customer personal data with no way for the subject to see it, correct it or remove it. Erasure re-authenticates and reports exactly what it deleted and what it retained; your security audit entries survive under Cyber Security Act 2024 s.29 and identify nobody, because that log never stores an email. Four were our checks failing, not our controls: a word-boundary fault meant networks: in a Docker Compose file could never match, so segmentation read as absent on every compose file we have ever scanned; the model card check excluded docs/, the folder model cards live in; the access control check did not know FastAPI’s scope-based authorisation, and a lookbehind refused any private helper, which blinded seventeen patterns at once; and the cookie consent check asked for a banner from a site with no tracking cookie, which would have been theatre. One was undeclared rather than absent: our audit chain is append-only and never purged, which exceeds the three years Act 854 asks for — we had simply never written the figure down. We found these because we run the scanner on ourselves and publish what it says.Resolved — verified 4 August 2026
Malaysian phone numbers in source
PDPA-PII-03
False positive. The matches are our own published business number in email footers — a company contact line, not anyone’s personal data.Reviewed — 2 August 2026
Credential-shaped value in a test file
OWASP-A02-B02
False positive. A throwaway password generated at run time inside a test, not a stored credential. The check reports it as a warning rather than hiding it, because a credential in test code is still a credential worth a human glance.Reviewed — 2 August 2026
Plain HTTP URL
OWASP-A05-W01
False positive. Both matches are internal: the container-to-host loopback address, and a private LAN address inside a remediation instruction. Neither leaves the machine.Reviewed — 2 August 2026

Why we publish this. A perfect score on a vendor’s own trust page tells you nothing. Every claim on this page is one we expect you to test: our checks have limits, our code has findings, and both are written down. If you want the same treatment for your own systems, the free check starts there.

How Strong Each Finding Is

A scanner that cries wolf is as useless as one that stays silent. In July 2026 our own audit produced three blocking findings that were wrong — our detection rules had matched the example patterns inside our own rulebook. A customer whose codebase is security tooling, or whose policy documents quote regulatory terminology, would have hit exactly the same thing.

So every detection finding now carries a strength, and we weakened the findings rather than deleting them:

StrengthWhat it meansEffect
HighThe evidence was found in executable code or live configuration.Blocks, as before
LowThe evidence was found in a rule definition, a test fixture, or prose — usually the pattern being described rather than a defect.Reported as a warning, never hidden

Two principles govern this. A silenced true positive is worse than a noisy false one, so nothing is ever suppressed — low-strength findings still appear in your report, labelled, for you to judge. And the SQL injection check now parses your code rather than pattern-matching it, so an interpolated value (injectable) is separated from an interpolated column name (not). We keep regression tests proving that real injections, real identity numbers, and real prohibited practices still block — because “fewer false positives” must never quietly become “less detection”.

Fixing this also exposed a bug worth admitting: one prohibited-practice rule could never match the very term it named, so it would have missed a real violation. The regression tests caught it. It is fixed.

How Deep Each Check Goes

Not every compliance check can prove the same thing, and we label which is which. Every finding AuditCore reports carries a verification depth:

DepthWhat a pass provesShare of our 222 checks
ImplementationThe control was found operating in code or configuration.48
DetectionA scan for a specific defect found none present.23
DocumentationThe practice is documented — evidence of policy, not proof of operation.151

Most compliance obligations are policy obligations, so documentation depth is the honest answer for most checks — but you should know which is which, and a report that blurs the two is worth less than one that does not. In July 2026 we reviewed all 178 of our own checks and relabelled every title that claimed more than it proved.

Security Controls

Passwords
bcrypt (salted)
Data in transit
TLS 1.2+ enforced
Session tokens
320-bit random
Session expiry
30 days
API auth
Per-customer bearer keys
Sign-in protection
Rate-limited (IP + account)
MFA
TOTP — all accounts
Network exposure
Loopback-only, via Cloudflare
Card data
Stripe only (PCI L1)
Backups
Nightly, restore-tested
✓
No hardcoded secretsOWASP A02 · verified by scanner
✓
Dependencies pinnedOWASP A06 · requirements.txt exact versions
✓
SQL parameterisedOWASP A03 · no string-concat queries
✓
Admin surface locked downOWASP A05 · internal/admin routes blocked at the edge, API docs disabled · verified Jul 2026
✓
MFA availableCIS Control 6 · TOTP two-factor on all customer accounts · shipped & verified Jul 2026
✓
Backups verified by restoreAct 854 · nightly consistent snapshots, integrity-checked and restore-tested on every run, replicated off-machine · verified 1 Aug 2026
✓
Session cookie is httpOnlyOWASP A07 · session token unreadable by page scripts, CSRF-guarded · shipped & verified 1 Aug 2026
✓
Incident response planAct 854 s.29 · documented
✓
CISO designatedAct 854 · Leon Chua, TechnoNet

Data We Hold About You

AuditCore applies data minimisation — we collect only what is necessary to deliver the service (PDPA 2010 s.6).

DataHeld by AuditCoreRetention
Email addressYes — stored for sign-in and report deliveryDuration of account + 12 months
Passwordbcrypt hash only — never plaintextDuration of account
Scan resultsYes — your job results and report90 days, then auto-purged · verified Jul 2026
Billing statusStripe customer ID + plan only7 years (tax law)
Payment cardNo — Stripe handles all card datan/a
IP addressServer logs only — security & abuse preventionRotating logs, short-term
Codebase contentProcessed only for the duration of the scanDeleted on scan completion

Sub-Processors

We use a small, fixed set of sub-processors. Each has been security-assessed under Act 854 s.33.

Sub-processorPurposeData sharedCertificationLocation
Stripe Inc.Payment processingBilling data (Stripe direct — no card data via us)PCI-DSS Level 1USA
Cloudflare Inc.Network / tunnel / CDNNetwork metadata onlyISO 27001USA
Qdrant (self-hosted)Vector databaseRegulatory corpus only — no personal dataSelf-hostedMalaysia
Ollama (self-hosted)Embedding modelRegulatory query text onlySelf-hostedMalaysia

Sub-processor list last reviewed: June 2026. We will notify customers of any material changes.

Data Residency

Data typeStored inNotes
Customer accounts, scan resultsMalaysia (local server)Primary SQLite databases on TechnoNet infrastructure
Regulatory corpus (Qdrant)Malaysia (local server)Self-hosted vector database
Billing recordsUSA (Stripe)PCI-DSS L1 · governed by Stripe DPA · PDPA s.129 compliant

Cross-border transfers to Stripe are governed by standard contractual clauses and comply with PDPA 2010 s.129. All AI inference runs on-premises — no data leaves Malaysia for AI processing.

Our PDPA Obligations as Your Data Processor

Under the PDPA s.9 (as amended in 2024), TechnoNet Sdn Bhd is directly liable as a data processor for personal data in its custody — fine up to RM 1,000,000 for breaches.

Our commitments to you:

Full Privacy Policy →  ·  Request DPA →

Security & Compliance Contacts

Data Protection Officer (DPO) & CISO Leon Chua · TechnoNet Sdn Bhd
[email protected]

General privacy & security enquiries [email protected] · +60193282828

PDP Commissioner (PDPC) Malaysia [email protected] · www.pdp.gov.my

Cybersecurity incidents (NACSA) cyber999.my · +603-8992 6969

Compliance Visibility — Disclaimer

AuditCore delivers Compliance Visibility — a scored report on every technical gap between your files and your stated policy. Your legal team determines whether that discrepancy constitutes a PDPA violation or regulatory non-compliance. AuditCore scan results are informational only and do not constitute legal advice.

Auditor-in-the-Loop: AuditCore generates the artefacts — scored findings, remediation guidance, and compliance assessment reports. The client's compliance officer is the human who reviews those artefacts and determines what action to take. TechnoNet Sdn Bhd accepts no liability for decisions made on the basis of AuditCore output without appropriate legal review.