Skip to content

Commit 51368de

Browse files
audit: classify 15 FFI unsafe findings as legitimate (PA001/PA007) (#20)
* audit: classify 15 FFI unsafe findings as legitimate (PA001/PA007) panic-attack assail flags 15 UnsafeCode/UnsafeFFI Critical/High findings under ffi/zig/src/ (dragonfly + cache bindings) — all at the Zig→C ABI boundary. Adds: - audits/assail-classifications.a2ml (15 entries, classification=legitimate-ffi) - audits/audit-ffi-2026-05-26.md Refs hyperpolymath/panic-attack#32. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(sweep): codeql actions matrix, safedom .res→.affine - Replace codeql.yml `javascript-typescript` matrix entry with `actions` (repo has no JS/TS source). Matches the canonical fix in rsr-template-repo#71 / mtpc#8 / thunderbird#75. - Replace examples/SafeDOMExample.res with the canonical examples/SafeDOMExample.affine (per gitbot-fleet#208 and the banned-ReScript anti-pattern policy). --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 18a9aa8 commit 51368de

4 files changed

Lines changed: 254 additions & 1 deletion

File tree

.github/workflows/codeql.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ jobs:
3030
fail-fast: false
3131
matrix:
3232
include:
33-
- language: javascript-typescript
33+
# Repo source is not JS/TS; scan workflow files via the
34+
# actions extractor (every repo has those). See hypatia rule
35+
# codeql_language_matrix_mismatch.
36+
- language: actions
3437
build-mode: none
3538

3639
steps:

audits/assail-classifications.a2ml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
;; SPDX-License-Identifier: MPL-2.0
2+
;; Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
(assail-classifications
5+
(metadata
6+
(version "1.0.0")
7+
(project "docudactyl")
8+
(last-updated "2026-05-26")
9+
(entries 15)
10+
(status "active"))
11+
12+
(classification
13+
(file "ffi/zig/src/dragonfly.zig")
14+
(category "UnsafeCode")
15+
(classification "legitimate-ffi")
16+
(audit "audits/audit-ffi-2026-05-26.md")
17+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
18+
(classification
19+
(file "ffi/zig/src/cache.zig")
20+
(category "UnsafeCode")
21+
(classification "legitimate-ffi")
22+
(audit "audits/audit-ffi-2026-05-26.md")
23+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
24+
(classification
25+
(file "ffi/zig/src/cache.zig")
26+
(category "UnsafeFFI")
27+
(classification "legitimate-ffi")
28+
(audit "audits/audit-ffi-2026-05-26.md")
29+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
30+
(classification
31+
(file "ffi/zig/src/lith_adapter.zig")
32+
(category "UnsafeCode")
33+
(classification "legitimate-ffi")
34+
(audit "audits/audit-ffi-2026-05-26.md")
35+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
36+
(classification
37+
(file "ffi/zig/src/redaction_recovery.zig")
38+
(category "UnsafeCode")
39+
(classification "legitimate-ffi")
40+
(audit "audits/audit-ffi-2026-05-26.md")
41+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
42+
(classification
43+
(file "ffi/zig/src/redaction_recovery.zig")
44+
(category "UnsafeFFI")
45+
(classification "legitimate-ffi")
46+
(audit "audits/audit-ffi-2026-05-26.md")
47+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
48+
(classification
49+
(file "ffi/zig/src/docudactyl_ffi.zig")
50+
(category "UnsafeCode")
51+
(classification "legitimate-ffi")
52+
(audit "audits/audit-ffi-2026-05-26.md")
53+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
54+
(classification
55+
(file "ffi/zig/src/docudactyl_ffi.zig")
56+
(category "UnsafeFFI")
57+
(classification "legitimate-ffi")
58+
(audit "audits/audit-ffi-2026-05-26.md")
59+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
60+
(classification
61+
(file "ffi/zig/src/prefetch.zig")
62+
(category "UnsafeCode")
63+
(classification "legitimate-ffi")
64+
(audit "audits/audit-ffi-2026-05-26.md")
65+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
66+
(classification
67+
(file "ffi/zig/src/stages.zig")
68+
(category "UnsafeCode")
69+
(classification "legitimate-ffi")
70+
(audit "audits/audit-ffi-2026-05-26.md")
71+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
72+
(classification
73+
(file "ffi/zig/src/stages.zig")
74+
(category "UnsafeFFI")
75+
(classification "legitimate-ffi")
76+
(audit "audits/audit-ffi-2026-05-26.md")
77+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
78+
(classification
79+
(file "ffi/zig/src/capnp.zig")
80+
(category "UnsafeCode")
81+
(classification "legitimate-ffi")
82+
(audit "audits/audit-ffi-2026-05-26.md")
83+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
84+
(classification
85+
(file "ffi/zig/src/ml_inference.zig")
86+
(category "UnsafeCode")
87+
(classification "legitimate-ffi")
88+
(audit "audits/audit-ffi-2026-05-26.md")
89+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
90+
(classification
91+
(file "ffi/zig/src/reextract.zig")
92+
(category "UnsafeCode")
93+
(classification "legitimate-ffi")
94+
(audit "audits/audit-ffi-2026-05-26.md")
95+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
96+
(classification
97+
(file "ffi/zig/src/gpu_ocr.zig")
98+
(category "UnsafeCode")
99+
(classification "legitimate-ffi")
100+
(audit "audits/audit-ffi-2026-05-26.md")
101+
(rationale "Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions."))
102+
)

audits/audit-ffi-2026-05-26.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!--
2+
SPDX-License-Identifier: MPL-2.0
3+
Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
4+
-->
5+
6+
# Audit: FFI unsafe blocks (docudactyl)
7+
8+
**Auditor**: Jonathan D.A. Jewell
9+
**Date**: 2026-05-26
10+
**Scope**: panic-attack assail Critical/High UnsafeCode/UnsafeFFI findings under `ffi/zig/src/`.
11+
**Registry**: `audits/assail-classifications.a2ml`.
12+
13+
## Rationale
14+
15+
Zig FFI layer (dragonfly/cache bindings). Each unsafe block is at the Zig→C ABI boundary required by the host language to call extern functions.
16+
17+
Classification is scoped to `ffi/zig/src/`; any unsafe block outside that root remains visible.
18+
19+
Refs hyperpolymath/panic-attack#32.

examples/SafeDOMExample.affine

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SafeDOMExample.affine — formally-verified DOM mounting (aspirational).
3+
//
4+
// This example shows the *shape* of SafeDOM consumer code in current
5+
// AffineScript syntax. The `SafeDOM` stdlib surface it references
6+
// (`mount_safe`, `mount_when_ready`, `mount_batch`,
7+
// `proven_selector_validate`, `proven_html_validate`, `mount`) is the
8+
// target of `affinescript#56` (DOM+Pixi binding survey) and does not
9+
// yet exist in the published stdlib. The file is therefore
10+
// parse-checked but not type-checked end-to-end until #56 lands the
11+
// bindings; `affinescript check` reports `Resolve.UndefinedModule
12+
// SafeDOM` which is expected.
13+
//
14+
// Previous versions of this file (estate-wide, 5 dialect variants)
15+
// pre-dated ADR-014 (qualified paths), ADR-016 (effect rows), and the
16+
// `#{`-record-literal sigil (ADR-215). They were retired in favour of
17+
// this canonical via the gitbot-fleet#208 sweep (2026-05-26).
18+
19+
module SafeDOMExample;
20+
21+
use prelude::{Option, Some, None, Result, Ok, Err};
22+
23+
// `Element` and friends are nominal extern types for now — the real
24+
// shape lands with affinescript#56.
25+
extern type Element;
26+
extern type Selector;
27+
extern type ValidHTML;
28+
29+
// Single-mount status, lifted from the host into a typed tag union.
30+
enum MountStatus {
31+
Mounted(Element),
32+
MountPointNotFound(String),
33+
InvalidSelector(String),
34+
InvalidHTML(String)
35+
}
36+
37+
// Batch-mount result.
38+
enum MountResult {
39+
Mounted([Element]),
40+
Failed(String)
41+
}
42+
43+
// Spec for one element in a batch mount.
44+
struct MountSpec {
45+
selector: String,
46+
html: String
47+
}
48+
49+
// SafeDOM's host-side surface, all IO-effecting. Callbacks are passed
50+
// as separate parameters (rather than a `MountCallbacks` record)
51+
// because fn-typed struct fields are not currently parser-supported.
52+
extern fn mount_safe(
53+
selector: ref String,
54+
html: ref String,
55+
on_success: fn(Element) -> (),
56+
on_error: fn(String) -> (),
57+
) -{IO}-> ();
58+
59+
extern fn mount_when_ready(
60+
selector: ref String,
61+
html: ref String,
62+
on_success: fn(Element) -> (),
63+
on_error: fn(String) -> (),
64+
) -{IO}-> ();
65+
66+
extern fn mount_batch(specs: ref [MountSpec]) -{IO}-> MountResult;
67+
68+
extern fn proven_selector_validate(s: ref String) -{IO}-> Result<Selector, String>;
69+
extern fn proven_html_validate(s: ref String) -{IO}-> Result<ValidHTML, String>;
70+
extern fn mount(sel: ref Selector, html: ref ValidHTML) -{IO}-> MountStatus;
71+
72+
extern fn array_for_each(xs: ref [Element], f: fn(Element) -> ()) -{IO}-> ();
73+
extern fn array_len(xs: ref [Element]) -> Int;
74+
75+
// Example 1 — basic mount with success/error branches.
76+
pub fn mount_app() -{IO}-> () {
77+
mount_safe(
78+
"#app",
79+
"<div><h1>Hello, World!</h1><p>Mounted safely with proofs.</p></div>",
80+
fn(el) -> () { Console::log("App mounted successfully"); },
81+
fn(err) -> () { Console::error("Mount failed: " ++ err); },
82+
);
83+
}
84+
85+
// Example 2 — defer until DOM ready.
86+
pub fn mount_when_dom_ready() -{IO}-> () {
87+
mount_when_ready(
88+
"#app",
89+
"<div class='container'><h1>App Title</h1></div>",
90+
fn(_el) -> () { Console::log("Mounted after DOM ready"); },
91+
fn(err) -> () { Console::error("Failed: " ++ err); },
92+
);
93+
}
94+
95+
// Example 3 — atomic batch mount.
96+
pub fn mount_multiple() -{IO}-> () {
97+
let specs = [
98+
MountSpec #{ selector: "#header", html: "<header><h1>Site Title</h1></header>" },
99+
MountSpec #{ selector: "#nav", html: "<nav><a href='/'>Home</a></nav>" },
100+
MountSpec #{ selector: "#main", html: "<main><p>Content here</p></main>" },
101+
MountSpec #{ selector: "#footer", html: "<footer>2026</footer>" },
102+
];
103+
104+
match mount_batch(specs) {
105+
Mounted(elements) => {
106+
Console::log("Batch mount succeeded");
107+
array_for_each(elements, fn(_el) -> () { Console::log(" element"); });
108+
},
109+
Failed(err) => {
110+
Console::error("Batch mount failed (atomic — none mounted): " ++ err);
111+
}
112+
}
113+
}
114+
115+
// Example 4 — explicit two-stage validation before mounting.
116+
pub fn mount_with_validation() -{IO}-> () {
117+
match proven_selector_validate("#my-app") {
118+
Err(e) => Console::error("Invalid selector: " ++ e),
119+
Ok(valid_selector) => match proven_html_validate("<div>Content</div>") {
120+
Err(e) => Console::error("Invalid HTML: " ++ e),
121+
Ok(valid_html) => match mount(valid_selector, valid_html) {
122+
Mounted(_el) => Console::log("Mounted with validated inputs"),
123+
MountPointNotFound(s) => Console::error("Element not found: " ++ s),
124+
InvalidSelector(_) => Console::error("impossible — already validated"),
125+
InvalidHTML(_) => Console::error("impossible — already validated"),
126+
},
127+
},
128+
}
129+
}

0 commit comments

Comments
 (0)