anonpenguin23 3676b000a6 chore: adopt DeBros DAO baseline rules + release 0.122.11
Standardization batch — no application code changes. Pulls in the
DeBros DAO baseline rules (v0.1.0, sha 51ce3f8) for supply-chain
defense and toolchain pinning.

Files added:
- DEBROS.md + debros.json — adopted-rules manifest
- .debros/compliance/{go,javascript-typescript,zig}.md — per-language
  compliance docs
- .github/workflows/security.yml — auto-detecting security CI
  (npm audit + go vulncheck), runs on main + weekly cron
- renovate.json — 30-day dependency cooldown, no auto-merge,
  vulnerability alerts bypass cooldown
- .nvmrc — pin Node 20.18.0
- vault/.zigversion — pin Zig 0.14.0
- sdk/.npmrc, website/.npmrc — supply-chain hardening
  (ignore-scripts, strict-peer-dependencies, save-exact, etc.)

Files modified:
- core/go.mod, os/agent/go.mod, website/invest-api/go.mod —
  add `toolchain go1.24.6` directive for reproducible builds
- VERSION + sdk/package.json — bump to 0.122.11
2026-05-12 11:10:10 +03:00

253 lines
11 KiB
Markdown

# Compliance — Zig
> The concrete files every Zig project must have to satisfy [DEBROS.md](../../DEBROS.md).
Zig is the youngest ecosystem in this rules set. The good news: Zig's design avoids most supply-chain attack vectors (no install-time scripts, dependencies are content-addressed by hash). The bad news: there's no mature vulnerability database, no Renovate support, and no convention-defining popular packages to follow. Compliance leans heavily on manual review.
> **Status:** Zig is pre-1.0 (current stable is `0.13.x` as of late 2025). Build APIs change between releases. Treat this document as a moving target — verify the directives below still work on your project's pinned compiler.
---
## Required files
### 1. `build.zig.zon` with explicit hashes for every dependency
**Tier 3 block.** Commits that add a dependency without an explicit hash are rejected.
Every dependency in `build.zig.zon` MUST include:
- `url` — the source tarball
- `hash` — the integrity hash Zig computes
```zig
.{
.name = "your-project",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.6.0.tar.gz",
.hash = "1220abc123def456...", // explicit, required
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
```
Zig's `zig build` will refuse to use a dependency whose downloaded content doesn't match the declared hash. This is equivalent to Go's `go.sum` and is the bedrock of Zig's supply-chain story.
**Never** use unhashed `path = ...` references to remote sources. Local path dependencies are fine for in-monorepo modules; remote sources must always be hashed.
### 2. `.zigversion` — pin the compiler
Convention file (read by `zigup`, `mise`, asdf via plugin):
```
0.13.0
```
CI MUST use the pinned compiler version, not "latest" or "master." Pre-1.0 Zig changes language semantics between minor versions; "latest" is not a safe default.
For projects on Zig master (development versions): commit the exact commit SHA, not "master."
### 3. Verify the compiler signature on install
The Zig compiler binary is signed with Andrew Kelley's minisign key, published at https://ziglang.org/download/. Every CI environment and every developer's machine MUST verify the signature when installing the compiler.
In CI:
```yaml
- name: Install Zig with signature verification
run: |
ZIG_VERSION=$(cat .zigversion)
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" -o zig.tar.xz
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz.minisig" -o zig.tar.xz.minisig
minisign -Vm zig.tar.xz -P RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
tar -xJf zig.tar.xz
```
The minisign public key above is the canonical one. Treat it as a pinned constant — if it changes, treat that change as a security event and verify out of band (mailing list, official site, multiple sources) before accepting.
### 4. Review every `build.zig`
Zig's `build.zig` is a Zig program. It runs at build time with **full system access** — it can read files, run subprocesses, hit the network. This is intentional (you can build C deps, run codegen, generate manifests) but it is also the equivalent of npm's `postinstall` problem at the build layer.
Rules:
- The project's own `build.zig` MUST be reviewed line by line in PRs (it's not "configuration," it's executable code with full power)
- Dependencies' `build.zig` files MUST be read when adding the dependency. Subprocess invocations (`std.process.Child`), file writes outside the cache, or network calls are red flags
- No dependency may invoke `std.process.Child` to run shell scripts at build time without explicit allowlisting in `debros.json.compliance.exceptions[]` with a one-line justification
This is the single largest supply-chain risk in Zig. The compiler can't tell "legit codegen" from "exfiltrate `~/.ssh/`." Human review is mandatory.
### 5. Lockfile-equivalent in CI
Zig doesn't have a separate lockfile; `build.zig.zon`'s `hash` fields ARE the lockfile. CI MUST refuse to build if `zig build` would update `build.zig.zon`:
```yaml
- name: Verify build.zig.zon is up to date
run: |
cp build.zig.zon build.zig.zon.expected
zig build --fetch
diff build.zig.zon build.zig.zon.expected
```
`zig build --fetch` resolves dependencies without compiling; if it would mutate `build.zig.zon`, the diff fails.
### 6. Compiler-version pinning in CI
Match the `.zigversion`:
```yaml
- name: Install pinned Zig
uses: mlugg/setup-zig@v1
with:
version-file: .zigversion
```
(`mlugg/setup-zig` is the community-maintained action with signature verification built in.)
---
## File-by-file checklist
| File | Path | Required? | Tier-3 block? |
|---|---|---|---|
| `build.zig.zon` with hashes for every remote dep | repo root | ✅ | ✅ |
| `.zigversion` | repo root | ✅ | — |
| CI workflow with compiler signature verification | `.github/workflows/security.yml` (or equivalent) | ✅ | — |
| CI step verifying `build.zig.zon` is up-to-date | same | ✅ | — |
---
## Code patterns to enforce
### Error handling — Zig's error unions are the friend
Per DEBROS.md §2.2 principle 6: errors carry context. Zig's error types are great but easy to misuse:
```zig
// Good — explicit error set, useful context
pub const ConnectError = error{
Timeout,
ConnectionRefused,
AddrInUse,
};
fn connectOlric(port: u16) ConnectError!Connection {
return Connection.init(port) catch |err| switch (err) {
error.Timeout => return error.Timeout,
error.ConnectionRefused => {
std.log.err("olric connection refused on port {d}", .{port});
return error.ConnectionRefused;
},
else => return err,
};
}
// Forbidden — silent swallow
fn connectOlric(port: u16) ?Connection {
return Connection.init(port) catch null; // hides why it failed
}
```
The `try` keyword bubbles errors; `catch` MUST handle them meaningfully (log + return, transform to a domain error, etc.) — never `catch unreachable` outside of provably-impossible cases.
### Allocator discipline
Per DEBROS.md §2.2 principle 4 (validate at boundaries, trust internal code): every public function that allocates takes an `std.mem.Allocator` parameter. No global state, no hidden allocations.
```zig
// Good
pub fn parseConfig(allocator: Allocator, source: []const u8) !Config { ... }
// Forbidden
pub fn parseConfig(source: []const u8) !Config {
const allocator = std.heap.page_allocator; // hidden global
...
}
```
Tests use `std.testing.allocator` (catches leaks). Production uses a configured allocator (general-purpose arena, fixed buffer, etc.).
### `defer` for cleanup; `errdefer` for error paths
Every allocation has a matching `defer free` (always cleanup) OR `errdefer free` (cleanup on error only, transfer ownership on success). Ad-hoc cleanup at the bottom of functions is forbidden.
### File and function sizes
Per DEBROS.md §2.1:
- Functions ≤50 lines
- Files ≤300 lines
There's no widely-used Zig linter for this yet. Enforce via PR review checklist until tooling lands.
### `comptime` discipline
`comptime` is powerful but easy to abuse. Rules:
- Use `comptime` for type-level computation (generic containers, compile-time validation of constants)
- Never use `comptime` for "performance" without measuring
- `comptime` code is subject to the same length and complexity caps as runtime code
- A `comptime` branch that grows past 30 lines is a code smell — extract to a named function
### Testing
Zig's built-in test runner is the standard:
```zig
test "parseCron rejects empty input" {
try std.testing.expectError(error.EmptyExpression, parseCron(""));
}
```
- Tests live alongside source (`test { ... }` blocks in the same file, OR `*_test.zig` files)
- Run via `zig build test`
- CI MUST run tests on every PR
- Unit suite total runtime <30s (DEBROS.md §2.4)
- No `std.time.sleep` in tests poll a readiness condition or use a fake clock
---
## Dependency additions
When adding a Zig dependency, the agent MUST:
1. **Pin a tag, not a branch.** `refs/tags/v0.6.0` is OK; `refs/heads/main` is not. Branch refs are mutable; tags should be immutable (verify the tag isn't a moving target on the upstream some repos rewrite tags).
2. **Read the dep's `build.zig`** for subprocess invocations, network calls, or file writes outside the cache. Each is a red flag that requires justification.
3. **Verify the hash.** After adding the dep, run `zig build --fetch` and confirm the computed hash matches what the upstream advertised.
4. **Check the maintainer's track record.** Single-author, low-star Zig repos are higher risk simply because the language attracts experimental code. Prefer deps with an active community.
5. **Note the lack of Renovate support.** Zig dep updates are manual. Document the upstream tag-tracking process in a comment in `build.zig.zon`.
---
## Migration from a stock Zig project
1. **Pin the compiler.** Add `.zigversion`.
2. **Audit `build.zig.zon`.** Every remote dependency must have a `hash`. Run `zig build --fetch` and copy the computed hashes in.
3. **Read every `build.zig`** in your dependency tree. Flag anything that runs subprocesses or hits the network at build time. Open issues upstream OR find alternatives.
4. **Add CI** with compiler signature verification and `zig build --fetch` lockfile check.
5. **Update `debros.json`** to record Zig compliance is satisfied. Note any `build.zig` exceptions you accepted in `compliance.exceptions[]`.
Expect first migration to take a day for projects with several deps the `build.zig` review is the slow part.
---
## Notes on Zig's supply-chain story
What Zig protects against (by design):
- **Hash-pinned dependencies.** `build.zig.zon` mutation is loud; a swapped dep fails to build.
- **No install-time scripts.** Dependencies don't run code when fetched (unlike npm postinstall).
- **No package registry to compromise.** Deps are URLs (usually GitHub tarballs); there's no central index to attack. Each upstream's compromise is isolated.
- **Cryptographically-signed compiler releases.** The official ziglang.org binaries are minisigned.
What Zig does NOT protect against:
- **`build.zig` running arbitrary code at build time.** This is the equivalent of npm postinstall, but always-on. Human review of every dep's `build.zig` is the only defense.
- **Compromised upstream repos.** Hash-pinning catches changes to *already-fetched* versions, but a malicious new release still has whatever malicious content it ships with. There's no `pip-audit` / `govulncheck` equivalent yet.
- **Tag rewriting.** Some upstreams rewrite tags. Hash-pinning catches this on re-fetch, but the social signal is missed. Prefer upstreams with a no-tag-rewrite policy.
- **Renovate support.** None yet. Track dep updates manually. Open a Renovate config issue upstream if your CI infra needs auto-PRs.
Zig is the youngest ecosystem here and tooling is still catching up. Until the Zig package registry (or an equivalent) emerges, manual review is the floor.