Fictitious example. ACME Embedded GmbH, its products, records and document IDs are invented for illustration. Use this as a structural model, not as a template to adopt unchanged — and not as legal advice.
SDL-STD-020 — Secure Coding Standard
| Document ID | SDL-STD-020, rev. 3.0 |
| Owner | Development Team Lead (Team Lead Firmware) |
| Approved by | Managing Director, 2026-03-14 |
| Reference | CRA Annex I Part I(2) (products delivered without known exploitable vulnerabilities, secure implementation); SDL-POL-001 Phase 3; verified in SDL-POL-001 Phase 4 |
| Applies to | All firmware written in C for ACME products (Zephyr RTOS targets and bare-metal), and to the companion-tool code where noted |
| Review | Annually, and whenever a released defect turns out to be preventable by a rule we did not have |
1. Scope and intent
This standard is what "secure implementation" means at ACME in concrete terms: which language subset applies, which constructs are banned, how secrets are handled in code, and which gates a merge request has to pass.
It is deliberately short. A coding standard nobody has read is a document; a coding standard that fits in a morning and is enforced by the pipeline is a control. Every rule here is either checked automatically or checked in review, and the rule states which.
2. Language subset — MISRA C:2012
2.1 Adopted subset. ACME applies MISRA C:2012 with the following categories:
| Category | Treatment |
|---|---|
| Mandatory | Enforced, no deviation possible |
| Required | Enforced; deviation possible under §2.2 |
| Advisory | Not enforced globally; applied within new modules where practical |
2.2 Deviations. A deviation is recorded in code at the point of the violation (/* MISRA deviation D-NNN: <rule> — <reason> */) and in the deviation register. It names the rule, the technical reason, the containment (what stops the violation from becoming a defect), and the approver. Required-category deviations are approved by the Development Team Lead; deviations in security-relevant code additionally by the PSL. A blanket file- or directory-level suppression is not a deviation and is not permitted.
2.3 Third-party code is out of scope for MISRA — Zephyr, mbedTLS and MCUboot are not ours to reformat. They are governed instead by the component policy (SDL-POL-005) and by the integration rules in §5.
3. Banned and restricted constructs
3.1 Banned outright (checked by the sast pipeline stage; a hit fails the build):
strcpy,strcat,sprintf,gets,alloca, and the unboundedscanffamily.- Dynamic allocation after initialisation. Heap use in steady-state firmware is banned; all buffers are statically sized or allocated once at start-up. This is a reliability rule as much as a security one — an embedded device that fragments its heap in year three fails in the field, not in test.
- Recursion in firmware. Stack depth must be statically analysable.
- Variable-length arrays.
- Compiler warnings. The firmware builds with
-Wall -Wextra -Werror; a warning is a build failure, not a note.
3.2 Restricted (permitted with justification in review):
- Pointer arithmetic outside a well-defined buffer abstraction.
- Casts that discard
constorvolatile. - Union-based type punning (use
memcpy).
4. Rules for security-relevant code
4.1 No hard-coded credentials, keys or secrets — not in source, not in headers, not in test fixtures, not in commit history. Per-device credentials are generated in production per SDL-STD-034; keys live in the MCU key vault or secure element. The sast stage runs a secret scanner over the diff and the history of every merge request; a hit blocks the merge and triggers a key-rotation assessment, because a secret in a branch is a secret that has left the building.
4.2 No home-grown cryptography. Ciphers, key derivation, random number generation and protocol implementations come from mbedTLS or the vendor's validated hardware engine. This includes "just a quick XOR for the config blob". Entropy comes from the hardware RNG; a seeded PRNG is never used for key material.
4.3 Input from outside a trust boundary is untrusted, without exception. Every parser that reads from the radio, the update endpoint, the UART maintenance protocol or the NFC tag validates length before content, treats all fields as attacker-controlled, and fails closed. These parsers are the fuzzing targets in SDL-POL-001 Phase 4 — a parser not reachable by the fuzzer is a parser that has not been tested.
4.4 Fail closed, and fail the same way. An authentication or verification path returns a single generic failure to the caller; branch-specific error codes leak the reason. Comparisons on secrets use constant-time primitives.
4.5 No secrets in logs, at any log level, including debug builds — debug builds get flashed onto units that leave the building more often than anyone plans for.
4.6 Clear memory holding key material after use, with a barrier the compiler cannot optimise away (mbedtls_platform_zeroize, not memset).
5. Integrating third-party code
- Components enter only through SDL-POL-005 and appear in the SBOM with a pinned version.
- The integration layer — not the component — is where our validation lives: our code checks the arguments it passes in and the values it gets back.
- Local patches to a third-party component are kept as separate, documented patch files, never as edits to a vendored tree. A silently edited upstream tree cannot be updated when the upstream fixes a CVE, which is precisely when it will need to be.
6. Review and pipeline gates
| Gate | Rule |
|---|---|
| Peer review | Mandatory on every merge request; the author cannot approve it (SDL-POL-001 §2) |
| PSL review | Additionally required for changes touching crypto, secure boot, the update client, the join/authentication path, or a trust boundary |
| `sast` stage | MISRA checks, banned-construct scan, secret scan, compiler warnings. Failure blocks the merge |
| Dependency/CVE scan | On every build against the SBOM |
| Release gate | Findings at CVSS ≥ 7.0 block the release (SDL-POL-001 Phase 4) |
Reviewer's brief for security-relevant changes: where does untrusted input enter, what happens to it before it is trusted, what is the failure path, and what does this change do to the attack surface documented in the threat model? A review that only checks style has checked the least important thing in the diff.
7. Records
Merge-request approvals, sast reports per build, the MISRA deviation register, and secret-scan findings with their rotation assessments. Quality records under SDL-POL-001 §6.
Revision history
| Rev | Date | Change | Approved |
|---|---|---|---|
| 2.0 | 2025-11-20 | MISRA C:2012 subset adopted; banned-function list moved into the pipeline | MD |
| 3.0 | 2026-03-14 | Secret scanning extended to branch history with a rotation trigger; heap ban in steady state; constant-time and zeroisation rules added; reviewer's brief added | MD |
