Skip to content

Commit fa360cf

Browse files
hyperpolymathclaude
andcommitted
fix(ffi): remove redundant @ptrCast from cartridge shims (CWE-704)
Sweeps all 114 vendored copies of cartridge_shim.zig in boj-server — including the canonical shared source ffi/zig/src/cartridge_shim.zig — to the blessed post-#146 idiom: std.mem.sliceTo(tool_name, 0) with [*c]const u8 test locals and no @ptrCast. Mirrors PR #89 in boj-server-cartridges, which is the split-out of these cartridges. Why the casts were redundant, not unsafe: a [*:0]const u8 sentinel pointer coerces implicitly to the [*c]const u8 that toolIs/invokeArgsNull accept, so @ptrCast(name) was a no-op widening — but a C-oriented scanner (Hypatia code_safety/zig_ptr_cast, CWE-704) still flags it as an unchecked pointer type conversion. Typing the local as [*c]const u8 up front removes the finding rather than suppressing it. Also folds in ffi/zig/src/abi_verify.zig (ABI-conformance tests C.6/C.7), which called shim.toolIs/invokeArgsNull via @ptrCast. NOTE: that file was modified in the shared working tree by a concurrent agent; it is included here because it is the identical CWE-704 fix — flag if it should land separately. Verification (zig 0.15.2, the CI-pinned version): - every cartridge_shim.zig compiles and passes `zig test` standalone; - the edited abi_verify C.6/C.7 idiom verified in isolation against the real shim (8/8), since the full `zig build test` graph does not compile under 0.15.2 for reasons unrelated to this change (see below). Out of scope, reported separately: (1) .github/workflows/zig-test.yml has been startup_failure (0s) on main since >=2026-07-09, so this repo's Zig CI validates nothing and this PR's zig-test check cannot pass on any branch; (2) catalogue/guardian/loader/coprocessor/sdp reference the removed std.atomic.Mutex and do not compile under the pinned 0.15.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 42fec37 commit fa360cf

115 files changed

Lines changed: 1833 additions & 1149 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cartridges/academic-workflow-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/aerie-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/affinescript-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/agent-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/airtable-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/arango-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/aws-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

cartridges/bofig-mcp/ffi/cartridge_shim.zig

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ pub fn invokeArgsNull(
5858
/// Compare a C-NUL-terminated tool-name pointer against a Zig string
5959
/// literal. Caller must have already verified `tool_name` is non-null
6060
/// (usually via `invokeArgsNull`).
61+
///
62+
/// Implementation note (CWE-704 fix, post-#146): uses
63+
/// `std.mem.sliceTo(ptr, 0)` which scans the C string up to the first
64+
/// NUL — no `@ptrCast` and no `[*:0]` re-typing. The earlier
65+
/// `std.mem.spanZ` call was removed in Zig 0.14+ and would not
66+
/// compile under the 0.15.1 CI pin.
6167
pub fn toolIs(tool_name: [*c]const u8, expected: []const u8) bool {
62-
const s = std.mem.span(@as([*:0]const u8, @ptrCast(tool_name)));
68+
const s = std.mem.sliceTo(tool_name, 0);
6369
return std.mem.eql(u8, s, expected);
6470
}
6571

@@ -124,19 +130,19 @@ test "writeResult: empty body" {
124130
}
125131

126132
test "toolIs: matches and rejects" {
127-
const name: [*:0]const u8 = "foo";
128-
try std.testing.expect(toolIs(@ptrCast(name), "foo"));
129-
try std.testing.expect(!toolIs(@ptrCast(name), "bar"));
130-
try std.testing.expect(!toolIs(@ptrCast(name), "foobar"));
131-
try std.testing.expect(!toolIs(@ptrCast(name), "fo"));
133+
const name: [*c]const u8 = "foo";
134+
try std.testing.expect(toolIs(name, "foo"));
135+
try std.testing.expect(!toolIs(name, "bar"));
136+
try std.testing.expect(!toolIs(name, "foobar"));
137+
try std.testing.expect(!toolIs(name, "fo"));
132138
}
133139

134140
test "invokeArgsNull: detects each null slot" {
135141
var buf: [4]u8 = undefined;
136142
var len: usize = 4;
137-
const name: [*:0]const u8 = "x";
138-
try std.testing.expect(!invokeArgsNull(@ptrCast(name), &buf, &len));
143+
const name: [*c]const u8 = "x";
144+
try std.testing.expect(!invokeArgsNull(name, &buf, &len));
139145
try std.testing.expect(invokeArgsNull(null, &buf, &len));
140-
try std.testing.expect(invokeArgsNull(@ptrCast(name), null, &len));
141-
try std.testing.expect(invokeArgsNull(@ptrCast(name), &buf, null));
146+
try std.testing.expect(invokeArgsNull(name, null, &len));
147+
try std.testing.expect(invokeArgsNull(name, &buf, null));
142148
}

0 commit comments

Comments
 (0)