Skip to content

Commit 3f927e5

Browse files
authored
Merge pull request #141 from backbay-labs/clawdstrike-go
feat(sdk): complete cross-SDK parity, conformance, and Go daemon/SIEM improvements
2 parents 89e5b98 + f24e5d0 commit 3f927e5

File tree

112 files changed

+14493
-2067
lines changed

Some content is hidden

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

112 files changed

+14493
-2067
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,35 @@ jobs:
130130
- name: Test
131131
run: cargo test --all --exclude sdr-integration-tests
132132

133+
sdk-conformance:
134+
name: SDK Conformance Vectors
135+
runs-on: ubuntu-latest
136+
needs: check
137+
steps:
138+
- uses: actions/checkout@v6
139+
140+
- name: Setup Go
141+
uses: actions/setup-go@v6
142+
with:
143+
go-version-file: packages/sdk/hush-go/go.mod
144+
cache: true
145+
cache-dependency-path: packages/sdk/hush-go/go.sum
146+
147+
- name: Setup Node.js
148+
uses: actions/setup-node@v6
149+
with:
150+
node-version: '24'
151+
cache: 'npm'
152+
cache-dependency-path: packages/sdk/hush-ts/package-lock.json
153+
154+
- name: Setup Python
155+
uses: actions/setup-python@v6
156+
with:
157+
python-version: "3.12"
158+
159+
- name: Run SDK conformance runner
160+
run: bash scripts/run-sdk-conformance.sh
161+
133162
tauri-rust-check:
134163
name: Tauri Rust Crates
135164
runs-on: ubuntu-latest
@@ -404,6 +433,7 @@ jobs:
404433
# Rust sources that do not emit stable LCOV entries in this job.
405434
grep -v '^apps/agent/src-tauri/' changed_rust_files.txt \
406435
| grep -v '^apps/desktop/src-tauri/' \
436+
| grep -v '^crates/bridges/hush-go-native/' \
407437
| grep -v '^packages/sdk/hush-py/hush-native/' \
408438
| grep -Ev '(^|/)tests/|_test\.rs$|_tests\.rs$|integration_tests\.rs$' \
409439
| grep -Ev '^crates/libs/hunt-(correlate|query|scan)/src/(lib|error)\.rs$' \

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,61 @@ if alerts:
390390
print(report.merkle_root)
391391
```
392392

393+
### Go
394+
395+
```bash
396+
go get github.com/backbay-labs/clawdstrike-go
397+
```
398+
399+
```go
400+
package main
401+
402+
import (
403+
"fmt"
404+
405+
clawdstrike "github.com/backbay-labs/clawdstrike-go"
406+
)
407+
408+
func main() {
409+
cs, err := clawdstrike.WithDefaults("strict")
410+
if err != nil {
411+
panic(err)
412+
}
413+
414+
decision := cs.CheckFileAccess("/home/user/.ssh/id_rsa")
415+
fmt.Println(decision.Status) // deny
416+
fmt.Println(decision.Message) // Access to forbidden path: ...
417+
}
418+
```
419+
420+
#### Daemon-backed Enforcement
421+
422+
```go
423+
package main
424+
425+
import (
426+
"fmt"
427+
"time"
428+
429+
clawdstrike "github.com/backbay-labs/clawdstrike-go"
430+
)
431+
432+
func main() {
433+
cs, err := clawdstrike.FromDaemonWithConfig("http://127.0.0.1:9876", clawdstrike.DaemonConfig{
434+
APIKey: "dev-token",
435+
Timeout: 5 * time.Second,
436+
RetryAttempts: 3,
437+
RetryBackoff: 200 * time.Millisecond,
438+
})
439+
if err != nil {
440+
panic(err)
441+
}
442+
443+
decision := cs.CheckEgress("api.openai.com", 443)
444+
fmt.Println(decision.Status) // allow / warn / deny
445+
}
446+
```
447+
393448
### OpenClaw Plugin
394449

395450
Clawdstrike ships as a first-class [OpenClaw](https://openclaw.com) plugin that enforces policy at the tool boundary — every tool call your agent makes is checked against your policy before execution.

apps/desktop/src/features/forensics/policy-workbench/state.test.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,113 @@ describe("policyWorkbenchReducer", () => {
153153
});
154154
expect(preserved.loadedVersion).toBe("1.4.0");
155155
});
156+
157+
it("preserves newer draft when save completes against an older snapshot", () => {
158+
const loaded = policyWorkbenchReducer(initialPolicyWorkbenchState, {
159+
type: "load_success",
160+
yaml: "version: \"1.2.0\"\nname: loaded",
161+
hash: "h1",
162+
version: "1.2.0",
163+
});
164+
const edited = policyWorkbenchReducer(loaded, {
165+
type: "edit",
166+
yaml: "version: \"1.2.0\"\nname: newer",
167+
});
168+
const saving = policyWorkbenchReducer(edited, { type: "save_start" });
169+
170+
const saved = policyWorkbenchReducer(saving, {
171+
type: "save_success_preserve_draft",
172+
loadedYaml: "version: \"1.2.0\"\nname: loaded",
173+
hash: "h2",
174+
});
175+
176+
expect(saved.isSaving).toBe(false);
177+
expect(saved.loadedHash).toBe("h2");
178+
expect(saved.loadedYaml).toContain("name: loaded");
179+
expect(saved.draftYaml).toContain("name: newer");
180+
});
181+
182+
it("clears stale loadError on save success", () => {
183+
const errored = policyWorkbenchReducer(initialPolicyWorkbenchState, {
184+
type: "load_error",
185+
message: "Failed to load policy",
186+
});
187+
expect(errored.loadError).toBe("Failed to load policy");
188+
189+
const saved = policyWorkbenchReducer(errored, {
190+
type: "save_success",
191+
yaml: "version: \"1.2.0\"\nname: saved",
192+
hash: "h3",
193+
});
194+
expect(saved.loadError).toBeUndefined();
195+
196+
const preserved = policyWorkbenchReducer(errored, {
197+
type: "save_success_preserve_draft",
198+
loadedYaml: "version: \"1.2.0\"\nname: saved",
199+
hash: "h3",
200+
});
201+
expect(preserved.loadError).toBeUndefined();
202+
});
203+
204+
it("preserves loadError while a new load is in-flight", () => {
205+
const errored = policyWorkbenchReducer(initialPolicyWorkbenchState, {
206+
type: "load_error",
207+
message: "Failed to load policy",
208+
});
209+
210+
const loading = policyWorkbenchReducer(errored, { type: "load_start" });
211+
expect(loading.loadError).toBe("Failed to load policy");
212+
});
213+
214+
it("clears stale saveError on save success paths", () => {
215+
const failed = policyWorkbenchReducer(initialPolicyWorkbenchState, {
216+
type: "save_error",
217+
message: "previous save failed",
218+
});
219+
expect(failed.saveError).toBe("previous save failed");
220+
221+
const saved = policyWorkbenchReducer(failed, {
222+
type: "save_success",
223+
yaml: "version: \"1.2.0\"\nname: saved",
224+
hash: "h3",
225+
});
226+
expect(saved.saveError).toBeUndefined();
227+
228+
const preserved = policyWorkbenchReducer(failed, {
229+
type: "save_success_preserve_draft",
230+
loadedYaml: "version: \"1.2.0\"\nname: saved",
231+
hash: "h3",
232+
});
233+
expect(preserved.saveError).toBeUndefined();
234+
});
235+
236+
it("refreshes loadedVersion on save success paths", () => {
237+
const loaded = policyWorkbenchReducer(initialPolicyWorkbenchState, {
238+
type: "load_success",
239+
yaml: 'version: "1.2.0"\nname: demo',
240+
hash: "h1",
241+
version: "1.2.0",
242+
});
243+
244+
const saved = policyWorkbenchReducer(loaded, {
245+
type: "save_success",
246+
yaml: 'version: "1.3.0"\nname: demo',
247+
hash: "h2",
248+
version: "1.3.0",
249+
});
250+
expect(saved.loadedVersion).toBe("1.3.0");
251+
252+
const edited = policyWorkbenchReducer(saved, {
253+
type: "edit",
254+
yaml: 'version: "1.4.0"\nname: demo',
255+
});
256+
const saving = policyWorkbenchReducer(edited, { type: "save_start" });
257+
const preserved = policyWorkbenchReducer(saving, {
258+
type: "save_success_preserve_draft",
259+
loadedYaml: 'version: "1.4.0"\nname: demo',
260+
hash: "h3",
261+
version: "1.4.0",
262+
});
263+
expect(preserved.loadedVersion).toBe("1.4.0");
264+
});
156265
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "hush-go-native"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "C FFI bridge for the Go SDK"
6+
license = "Apache-2.0"
7+
8+
[lib]
9+
crate-type = ["staticlib", "cdylib"]
10+
11+
[dependencies]
12+
hush-core = { workspace = true }
13+
clawdstrike = { workspace = true }
14+
libc = "0.2"
15+
serde_json = { workspace = true }
16+
hex = { workspace = true }
17+
tokio = { workspace = true }
18+
base64 = { workspace = true }
19+
20+
[build-dependencies]
21+
cbindgen = "0.28"
22+
23+
[lints]
24+
workspace = true
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
cd "$(dirname "$0")"
4+
5+
REPO_ROOT="$(cd ../../.. && pwd)"
6+
DEST_INCLUDE_DIR="$REPO_ROOT/packages/sdk/hush-go/native/include"
7+
DEST_LIB_DIR="$REPO_ROOT/packages/sdk/hush-go/native/lib"
8+
9+
mkdir -p "$DEST_INCLUDE_DIR" "$DEST_LIB_DIR"
10+
11+
# Build static library
12+
cargo build --release -p hush-go-native
13+
14+
# Generate C header
15+
cbindgen --config cbindgen.toml --crate hush-go-native --output "$DEST_INCLUDE_DIR/hush_go_native.h"
16+
17+
# Copy static library (platform-dependent name)
18+
if [[ "$(uname)" == "Darwin" ]]; then
19+
cp "$REPO_ROOT/target/release/libhush_go_native.a" "$DEST_LIB_DIR/"
20+
cp "$REPO_ROOT/target/release/libhush_go_native.dylib" "$DEST_LIB_DIR/" 2>/dev/null || true
21+
else
22+
cp "$REPO_ROOT/target/release/libhush_go_native.a" "$DEST_LIB_DIR/"
23+
cp "$REPO_ROOT/target/release/libhush_go_native.so" "$DEST_LIB_DIR/" 2>/dev/null || true
24+
fi
25+
26+
echo "Build complete. Static library and header copied to packages/sdk/hush-go/native/"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language = "C"
2+
include_guard = "HUSH_GO_NATIVE_H"
3+
autogen_warning = "/* Auto-generated by cbindgen. Do not edit. */"
4+
5+
[export]
6+
include = []
7+
exclude = []

0 commit comments

Comments
 (0)