@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
package/dist/audit.js ADDED
@@ -0,0 +1,416 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/framework-zephyr — security baseline audit
3
+ //
4
+ // `typecad-hal audit` — evaluate the last build's MERGED Kconfig
5
+ // (<buildDir>/zephyr/.config, the as-built truth nothing else reads) against
6
+ // a curated Zephyr security baseline, in the spirit of the EU Cyber
7
+ // Resilience Act's essential requirements (secure by default, attack surface
8
+ // minimised, no hard-coded credentials, functioning crypto underpinnings).
9
+ //
10
+ // Architecture mirrors the AUTOSAR compliance module and the sbom command:
11
+ // rules are DATA evaluated over facts, never board-name branches; justified
12
+ // exceptions are DEVIATIONS recorded in a committed waiver file
13
+ // (.typecad-hal/audit-waivers.json) rather than suppressed findings; the
14
+ // presenter never calls process.exit() and sets process.exitCode under
15
+ // --strict only for unwaived high/medium findings. A machine-readable
16
+ // sidecar (<buildDir>/security-audit.json) is written on every run — the
17
+ // input the CRA technical file and the future VEX/audit feed consume.
18
+ // ---------------------------------------------------------------------------
19
+ import { readFileSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs';
20
+ import * as path from 'node:path';
21
+ import * as ui from '@typecad/cuttlefish/utils/ui';
22
+ import { loadTypecadConfig } from '@typecad/cuttlefish/config-loader';
23
+ import { findCompileCommandsPath } from './west-inventory.js';
24
+ /** Parse a merged Kconfig file: `CONFIG_X=y` (true), `# CONFIG_X is not set`
25
+ * (explicit false), `CONFIG_X=123` / `CONFIG_X="s"` (value). Comment blocks
26
+ * and everything else are ignored. Absent symbols are simply not in the map
27
+ * (Kconfig default applies — treat as "not enabled" for y/n symbols). */
28
+ export function parseMergedConfig(text) {
29
+ const out = new Map();
30
+ for (const line of text.split(/\r?\n/)) {
31
+ const notSet = line.match(/^#\s*CONFIG_([A-Z0-9_]+) is not set$/);
32
+ if (notSet) {
33
+ out.set(`CONFIG_${notSet[1]}`, false);
34
+ continue;
35
+ }
36
+ const set = line.match(/^CONFIG_([A-Z0-9_]+)=(.*)$/);
37
+ if (set) {
38
+ const raw = set[2].trim();
39
+ if (raw === 'y')
40
+ out.set(`CONFIG_${set[1]}`, true);
41
+ else if (raw === 'n')
42
+ out.set(`CONFIG_${set[1]}`, false);
43
+ else if (/^-?\d+$/.test(raw))
44
+ out.set(`CONFIG_${set[1]}`, Number(raw));
45
+ else if (/^".*"$/s.test(raw))
46
+ out.set(`CONFIG_${set[1]}`, raw.slice(1, -1));
47
+ else
48
+ out.set(`CONFIG_${set[1]}`, raw);
49
+ }
50
+ }
51
+ return out;
52
+ }
53
+ /** Sweep a resolved devicetree for every `compatible` string — the enabled
54
+ * hardware the firmware actually wired (attack-surface inventory input).
55
+ * Multi-string compatibles ("a,b", "a,b-fallback") contribute EVERY entry
56
+ * (the fallback binds the same node); nodes marked status = "disabled"
57
+ * contribute nothing. Node scoping is a brace-depth walk: every block is a
58
+ * node, and only its OWN properties (nested child blocks stripped) decide
59
+ * its status/compatibles. */
60
+ export function sweepCompatibles(dtsText) {
61
+ const found = new Set();
62
+ // The block's own properties: the text with nested child blocks removed.
63
+ const ownProps = (body) => {
64
+ let out = '';
65
+ let depth = 0;
66
+ for (const ch of body) {
67
+ if (ch === '{') {
68
+ depth++;
69
+ continue;
70
+ }
71
+ if (ch === '}') {
72
+ depth--;
73
+ continue;
74
+ }
75
+ if (depth === 0)
76
+ out += ch;
77
+ }
78
+ return out;
79
+ };
80
+ // Visit EVERY node block (advancing past '{' only, so nested nodes are
81
+ // found by later iterations too).
82
+ let i = 0;
83
+ while (i < dtsText.length) {
84
+ const open = dtsText.indexOf('{', i);
85
+ if (open < 0)
86
+ break;
87
+ let depth = 1;
88
+ let j = open + 1;
89
+ while (j < dtsText.length && depth > 0) {
90
+ const c = dtsText[j];
91
+ if (c === '{')
92
+ depth++;
93
+ else if (c === '}')
94
+ depth--;
95
+ j++;
96
+ }
97
+ const props = ownProps(dtsText.slice(open + 1, Math.min(j, dtsText.length) - (depth === 0 ? 1 : 0)));
98
+ if (!/status\s*=\s*"disabled"/.test(props)) {
99
+ for (const m of props.matchAll(/compatible\s*=\s*((?:"[^"]*"\s*,?\s*)+)/g)) {
100
+ for (const s of m[1].matchAll(/"([^"]+)"/g))
101
+ found.add(s[1]);
102
+ }
103
+ }
104
+ i = open + 1;
105
+ }
106
+ return [...found].sort();
107
+ }
108
+ const on = (f, name) => f.config.get(name) === true;
109
+ const anyOn = (f, ...names) => names.some((n) => on(f, n));
110
+ /**
111
+ * The Zephyr security baseline. Rules read ONLY config/devicetree facts —
112
+ * never a board name or SoC family (all-boards-are-equal). Every rule must
113
+ * be waivable with a recorded justification: real products have real
114
+ * exceptions, and a conformity assessor wants to see them documented, not
115
+ * hidden.
116
+ */
117
+ export const SECURITY_RULES = [
118
+ {
119
+ id: 'zephyr.gdbstub',
120
+ title: 'remote debug stub compiled into the image',
121
+ severity: 'high',
122
+ requirement: 'Annex I 1.2(a) — attack surface minimised; no remotely exploitable debug interfaces',
123
+ remediation: 'disable CONFIG_GDBSTUB for production builds (gate it behind a debug build type)',
124
+ evaluate: (f) => (on(f, 'CONFIG_GDBSTUB') ? 'CONFIG_GDBSTUB=y' : null),
125
+ },
126
+ {
127
+ id: 'zephyr.bt-fixed-passkey',
128
+ title: 'static Bluetooth pairing passkey (hard-coded credential)',
129
+ severity: 'high',
130
+ requirement: 'Annex I 1.2(f) — no hard-coded credentials; unique per-device secrets',
131
+ remediation: 'remove CONFIG_BT_FIXED_PASSKEY and use per-device pairing (just-in-time/numeric comparison)',
132
+ evaluate: (f) => {
133
+ const v = f.config.get('CONFIG_BT_FIXED_PASSKEY');
134
+ return typeof v === 'number' ? `CONFIG_BT_FIXED_PASSKEY=${v}` : null;
135
+ },
136
+ },
137
+ {
138
+ id: 'zephyr.entropy-missing',
139
+ title: 'crypto/networking enabled without a hardware entropy source',
140
+ severity: 'high',
141
+ requirement: 'Annex I 1.2(c) — cryptographic primitives backed by proper randomness',
142
+ remediation: 'enable CONFIG_ENTROPY_GENERATOR (the board SoC entropy driver) — keys and nonces '
143
+ + 'derived without it are predictable',
144
+ evaluate: (f) => {
145
+ if (on(f, 'CONFIG_ENTROPY_GENERATOR'))
146
+ return null;
147
+ const consumers = ['CONFIG_BT', 'CONFIG_NETWORKING', 'CONFIG_MBEDTLS', 'CONFIG_TINYCRYPT'].filter((n) => on(f, n));
148
+ if (consumers.length === 0)
149
+ return null;
150
+ return '# CONFIG_ENTROPY_GENERATOR is not set (while '
151
+ + consumers.map((c) => `${c}=y`).join(', ')
152
+ + ')';
153
+ },
154
+ },
155
+ {
156
+ id: 'zephyr.shell-enabled',
157
+ title: 'interactive shell compiled in',
158
+ severity: 'high',
159
+ requirement: 'Annex I 1.2(a) — attack surface minimised; no unnecessary interactive interfaces',
160
+ remediation: 'disable CONFIG_SHELL for production builds, or restrict it to a debug build type',
161
+ evaluate: (f) => (on(f, 'CONFIG_SHELL') ? 'CONFIG_SHELL=y' : null),
162
+ },
163
+ {
164
+ id: 'zephyr.network-no-tls',
165
+ title: 'network stack enabled without a TLS library',
166
+ severity: 'medium',
167
+ requirement: 'Annex I 1.2(c)/(e) — confidential communication over networks',
168
+ remediation: 'enable CONFIG_MBEDTLS (mbedTLS SSL) for networked firmware, or document why the '
169
+ + 'traffic may stay in cleartext',
170
+ evaluate: (f) => on(f, 'CONFIG_NETWORKING') && !on(f, 'CONFIG_MBEDTLS')
171
+ ? 'CONFIG_NETWORKING=y, # CONFIG_MBEDTLS is not set'
172
+ : null,
173
+ },
174
+ {
175
+ id: 'zephyr.watchdog-absent',
176
+ title: 'no watchdog configured',
177
+ severity: 'medium',
178
+ requirement: 'Annex I 1.3(b) — resilience and availability of essential functions',
179
+ remediation: 'enable CONFIG_WATCHDOG and feed it from the app (hal: `new Watchdog(...)` on a '
180
+ + 'watchdog-capable board) so hangs recover instead of bricking the product',
181
+ evaluate: (f) => (on(f, 'CONFIG_WATCHDOG') ? null : '# CONFIG_WATCHDOG is not set'),
182
+ },
183
+ {
184
+ id: 'zephyr.console-enabled',
185
+ title: 'diagnostic console enabled',
186
+ severity: 'low',
187
+ requirement: 'Annex I 1.2(a) — attack surface minimised (informational: consoles are often '
188
+ + 'intentional in the field)',
189
+ remediation: 'if the console is not needed in production, disable it (CONFIG_UART_CONSOLE / '
190
+ + 'CONFIG_USB_CONSOLE / CONFIG_RTT_CONSOLE)',
191
+ evaluate: (f) => {
192
+ const which = ['CONFIG_UART_CONSOLE', 'CONFIG_USB_CONSOLE', 'CONFIG_RTT_CONSOLE'].filter((n) => on(f, n));
193
+ return which.length > 0 ? which.join(', ') + ' enabled' : null;
194
+ },
195
+ },
196
+ {
197
+ id: 'zephyr.hardening-off',
198
+ title: 'compiler hardening stack not enabled',
199
+ severity: 'low',
200
+ requirement: 'Annex I 1.2(b) — reduce exploitability of residual vulnerabilities',
201
+ remediation: 'enable CONFIG_HARDENING (stack canaries and friends) unless the overhead is '
202
+ + 'measured to matter',
203
+ evaluate: (f) => (on(f, 'CONFIG_HARDENING') ? null : 'CONFIG_HARDENING not enabled'),
204
+ },
205
+ ];
206
+ /** Evaluate the baseline over the facts. Order follows rule severity. */
207
+ export function evaluateSecurityRules(facts) {
208
+ const order = { high: 0, medium: 1, low: 2 };
209
+ const findings = [];
210
+ for (const rule of SECURITY_RULES) {
211
+ const evidence = rule.evaluate(facts);
212
+ if (evidence !== null) {
213
+ findings.push({
214
+ rule: rule.id,
215
+ title: rule.title,
216
+ severity: rule.severity,
217
+ requirement: rule.requirement,
218
+ remediation: rule.remediation,
219
+ evidence,
220
+ });
221
+ }
222
+ }
223
+ findings.sort((a, b) => order[a.severity] - order[b.severity]);
224
+ return findings;
225
+ }
226
+ export function parseWaiverFile(text) {
227
+ if (!text)
228
+ return { waivers: [] };
229
+ try {
230
+ const parsed = JSON.parse(text);
231
+ if (!parsed || !Array.isArray(parsed.waivers)) {
232
+ return { waivers: [], error: 'waiver file must be `{ "waivers": [...] }` — ignored' };
233
+ }
234
+ const waivers = [];
235
+ for (const w of parsed.waivers) {
236
+ if (w &&
237
+ typeof w === 'object' &&
238
+ typeof w.rule === 'string' &&
239
+ typeof w.justification === 'string') {
240
+ const entry = w;
241
+ if (entry.justification.trim().length === 0) {
242
+ return { waivers: [], error: `waiver for ${entry.rule} has an empty justification — ignored` };
243
+ }
244
+ waivers.push(entry);
245
+ }
246
+ }
247
+ return { waivers };
248
+ }
249
+ catch {
250
+ return { waivers: [], error: 'waiver file is not valid JSON — ignored' };
251
+ }
252
+ }
253
+ /** Find .typecad-hal/audit-waivers.json, walking up from `cwd` (bounded). */
254
+ export function findWaiverFile(readFile, cwd) {
255
+ let dir = path.resolve(cwd);
256
+ for (let i = 0; i < 5; i++) {
257
+ const p = path.join(dir, '.typecad-hal', 'audit-waivers.json');
258
+ if (readFile(p))
259
+ return p;
260
+ const parent = path.dirname(dir);
261
+ if (parent === dir)
262
+ break;
263
+ dir = parent;
264
+ }
265
+ return undefined;
266
+ }
267
+ function defaultAuditRunner(cwd = process.cwd()) {
268
+ const readFile = (p) => {
269
+ try {
270
+ return readFileSync(p, 'utf8');
271
+ }
272
+ catch {
273
+ return undefined;
274
+ }
275
+ };
276
+ const readdir = (d) => {
277
+ try {
278
+ return readdirSync(d);
279
+ }
280
+ catch {
281
+ return [];
282
+ }
283
+ };
284
+ return {
285
+ cwd,
286
+ compileCommandsPath: () => findCompileCommandsPath(readFile, readdir, cwd),
287
+ readFile,
288
+ };
289
+ }
290
+ export function buildAuditReport(args) {
291
+ const f = args.facts;
292
+ const consoleType = on(f, 'CONFIG_UART_CONSOLE')
293
+ ? 'uart'
294
+ : on(f, 'CONFIG_USB_CONSOLE')
295
+ ? 'usb'
296
+ : on(f, 'CONFIG_RTT_CONSOLE')
297
+ ? 'rtt'
298
+ : 'none';
299
+ return {
300
+ schema: 'typecad-hal/security-audit@1',
301
+ generated: args.generated ?? new Date().toISOString(),
302
+ buildDir: args.buildDir,
303
+ summary: {
304
+ console: consoleType,
305
+ shell: on(f, 'CONFIG_SHELL'),
306
+ network: on(f, 'CONFIG_NETWORKING'),
307
+ bluetooth: on(f, 'CONFIG_BT'),
308
+ usbDevice: anyOn(f, 'CONFIG_USB_DEVICE_STACK', 'CONFIG_USB_DEVICE_DRIVER'),
309
+ entropy: on(f, 'CONFIG_ENTROPY_GENERATOR'),
310
+ watchdog: on(f, 'CONFIG_WATCHDOG'),
311
+ hardware: f.compatibles,
312
+ },
313
+ findings: args.findings,
314
+ deviations: args.deviations,
315
+ rulesEvaluated: SECURITY_RULES.length,
316
+ waiverFile: args.waiverFile,
317
+ };
318
+ }
319
+ let testRunner;
320
+ /** @internal Test-only override of the default runner. */
321
+ export function __setAuditRunnerForTest(runner) {
322
+ testRunner = runner;
323
+ }
324
+ function printFinding(f, waived) {
325
+ // ui.printWarning already stamps its own "! " marker.
326
+ ui.printWarning(`${waived ? 'waived ' : ''}[${f.severity}] ${f.rule} — ${f.title}`);
327
+ ui.printInfo(` requirement: ${f.requirement}`);
328
+ ui.printInfo(` evidence: ${f.evidence}`);
329
+ ui.printInfo(` fix: ${f.remediation}`);
330
+ if (waived)
331
+ ui.printInfo(` waived: ${waived}`);
332
+ }
333
+ /** `typecad-hal audit` presenter. */
334
+ export function runAuditPresenter(options) {
335
+ const runner = testRunner ?? defaultAuditRunner();
336
+ const cwd = runner.cwd ?? process.cwd();
337
+ const ccPath = runner.compileCommandsPath ? runner.compileCommandsPath() : undefined;
338
+ const buildDir = ccPath ? path.dirname(ccPath) : undefined;
339
+ const configText = buildDir ? runner.readFile(path.join(buildDir, 'zephyr', '.config')) : undefined;
340
+ if (!buildDir || configText === undefined) {
341
+ ui.printError('No build found — audit reads the merged zephyr/.config of the last build. Run typecad-hal build first.');
342
+ process.exitCode = 1;
343
+ return;
344
+ }
345
+ const dtsText = runner.readFile(path.join(buildDir, 'zephyr', 'zephyr.dts'));
346
+ const facts = {
347
+ config: parseMergedConfig(configText),
348
+ compatibles: dtsText ? sweepCompatibles(dtsText) : [],
349
+ };
350
+ const all = evaluateSecurityRules(facts);
351
+ const waiverPath = findWaiverFile(runner.readFile, cwd);
352
+ const { waivers, error: waiverError } = parseWaiverFile(waiverPath ? runner.readFile(waiverPath) : undefined);
353
+ const waivedByRule = new Map(waivers.map((w) => [w.rule, w]));
354
+ const findings = all.filter((f) => !waivedByRule.has(f.rule));
355
+ const deviations = all
356
+ .filter((f) => waivedByRule.has(f.rule))
357
+ .map((f) => ({ ...f, ...waivedByRule.get(f.rule) }));
358
+ const report = buildAuditReport({ buildDir, facts, findings, deviations, waiverFile: waiverPath });
359
+ // Sidecar first (best-effort, never fatal), then the human/JSON surface.
360
+ try {
361
+ mkdirSync(buildDir, { recursive: true });
362
+ writeFileSync(path.join(buildDir, 'security-audit.json'), JSON.stringify(report, null, 2) + '\n');
363
+ }
364
+ catch {
365
+ /* best-effort sidecar */
366
+ }
367
+ // --strict: unwaived high/medium findings fail. Waived findings are
368
+ // recorded deviations, not failures.
369
+ const failing = findings.filter((f) => f.severity === 'high' || f.severity === 'medium');
370
+ if (options.strict && failing.length > 0) {
371
+ process.exitCode = 1;
372
+ }
373
+ if (options.json) {
374
+ console.log(JSON.stringify(report, null, 2));
375
+ return;
376
+ }
377
+ ui.printHeader();
378
+ ui.printStep('Auditing the merged build configuration (security baseline)');
379
+ let boardTarget;
380
+ try {
381
+ boardTarget = loadTypecadConfig(process.cwd())?.buildTarget;
382
+ }
383
+ catch {
384
+ /* best-effort */
385
+ }
386
+ if (boardTarget)
387
+ ui.printInfo(`board ............. ${boardTarget}`);
388
+ const s = report.summary;
389
+ ui.printInfo(`surface ........... console ${s.console} · shell ${s.shell ? 'ON' : 'off'} · net ${s.network ? 'ON' : 'off'} · ` +
390
+ `BLE ${s.bluetooth ? 'ON' : 'off'} · USB device ${s.usbDevice ? 'ON' : 'off'}`);
391
+ ui.printInfo(`underpinnings ..... entropy ${s.entropy ? 'ok' : 'OFF'} · watchdog ${s.watchdog ? 'ok' : 'OFF'} · ` +
392
+ `${s.hardware.length} wired compatible(s)`);
393
+ if (waiverError)
394
+ ui.printWarning(`waiver file: ${waiverError}`);
395
+ for (const f of findings)
396
+ printFinding(f);
397
+ for (const d of deviations) {
398
+ printFinding(d, `${d.justification}${d.date ? ` (${d.date})` : ''}`);
399
+ }
400
+ if (all.length === 0) {
401
+ ui.printSuccess(`baseline clean — ${report.rulesEvaluated} rules evaluated, 0 findings.`);
402
+ }
403
+ else {
404
+ ui.printWarning(`${findings.length} finding(s) (${failing.length} unwaived high/medium), ` +
405
+ `${deviations.length} waived deviation(s).`);
406
+ }
407
+ if (waiverPath) {
408
+ ui.printInfo(`waivers .......... ${waiverPath}`);
409
+ }
410
+ ui.printInfo(`sidecar .......... ${path.join(buildDir, 'security-audit.json')}`);
411
+ if (options.strict) {
412
+ ui.printInfo(failing.length > 0
413
+ ? '--strict .......... FAIL (unwaived high/medium findings above)'
414
+ : '--strict .......... pass');
415
+ }
416
+ }
@@ -19,14 +19,6 @@ export declare function socOfTarget(target: string): string;
19
19
  * network core declares no SAADC).
20
20
  */
21
21
  export declare function nrfSaadcAinPads(soc: string, identifier: string): readonly [number, number][] | undefined;
22
- /**
23
- * ESP32 DAC channel→GPIO pad map per SoC — [channel, pad] pairs, silicon-
24
- * fixed (no pinctrl group; the DAC outputs through the RTC IO mux on a fixed
25
- * pad). Only the original ESP32 and the ESP32-S2 carry the 8-bit DAC; the
26
- * S3/C3/C6/H2 dropped it. Sourced from the HAL's dac_periph.c
27
- * (`dac_channel_io_num[]`), like the nRF SAADC table is from the PS.
28
- */
29
- export declare function esp32DacPins(soc: string): readonly [number, number][] | undefined;
30
22
  /**
31
23
  * Parse a datasheet pin name into (controller nodelabel, controller-relative
32
24
  * bit) per the soc's naming family — the inverse of the sweep naming:
@@ -75,7 +67,7 @@ export interface UserBoardFacts {
75
67
  }[];
76
68
  };
77
69
  }
78
- /** The whole cuttlefish.facts.json shape. */
70
+ /** The whole typecad-hal.facts.json shape. */
79
71
  export interface UserFactsFile {
80
72
  boards: Record<string, UserBoardFacts>;
81
73
  }