Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,40 @@ permissions:
contents: read

jobs:
# The source can be correct while the compiler/runtime or a linked module carries a
# reachable vulnerability. Pin govulncheck and run it under the exact patch toolchain
# from go.mod so an old standard library cannot produce a fully-green release again.
# This is deliberately a separate lane: it consults the Go vulnerability database,
# while make verify remains fast and hermetic.
vuln:
name: govulncheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Reachable vulnerability scan
run: go run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./...

# Exercise the same curl/checksum/install path published in the README against a
# locally-built release asset. The script also proves missing and mismatched checksum
# records fail closed, so release-integrity regressions are caught before tagging.
installer:
name: release + installer integrity (local E2E)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build and verify the four-asset release set
run: bash test/release-assets-e2e.sh
- name: Verify and install a local release asset
run: bash test/install-e2e.sh

verify:
name: make verify
runs-on: ubuntu-latest
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Aggregate a signed-off checksums manifest
# One SHA256SUMS covering every binary, alongside the per-binary .sha256 files,
# so a consumer can verify the whole release set at once.
run: |
( cd dist && sha256sum nilcore-* 2>/dev/null | grep -v '\.sha256$' > SHA256SUMS || true )
echo "--- SHA256SUMS ---"; cat dist/SHA256SUMS
- name: Verify assets and assemble the checksums manifest
# Refuse to publish a partial or corrupt release. The installer requires the
# per-binary records, and SHA256SUMS must cover exactly the four supported targets.
run: scripts/verify-release-assets.sh dist
- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ On a release, the maintainer moves the accumulated `[Unreleased]` entries into a

## [Unreleased]

- **security-release-hardening** — Pin the build/release floor to patched Go 1.25.12 and `x/sys` 0.44.0, add a pinned reachable-vulnerability CI gate, make release publication refuse missing/corrupt platform assets or checksum records, and add real four-target release + installer E2Es proving valid install plus fail-closed partial-release, missing-checksum, and tampered-binary paths; refresh the public toolchain and state snapshot. _Owns:_ `go.mod`, `go.sum`, `.github/workflows/{ci,release}.yml`, `scripts/{install,verify-release-assets}.sh`, `test/{install,release-assets}-e2e.sh`, `README.md`, `STATE.md`, `docs/PREREQUISITES.md`. _(security / release hardening)_

- **docs(state-of-the-project): reconcile every document with the code as it actually is.** A ground-truth pack was extracted from the tree (CLI surface, the full env-var inventory incl. the prefix-constructed `NILCORE_{CODEX,CLAUDE}_{MODEL,EFFORT}`, package inventory + metrics, emitted event kinds, invariant enforcement sites, shipped-vs-gated status), then all 30 docs were audited against it and each repair was **independently refuted** by a second reader; 15 surviving defects were re-fixed. Headline corrections: `router.Classify(goal)` returns `run|build|swarm` — **not** `|decompose` (`Decompose` is a fourth `Preset`, opt-in only, excluded from `All()`), a claim three contract files asserted wrongly; `docs/ARCHITECTURE.md` overstated the container egress control as SSRF-proof/IP-layer when `AllowEgressVia` only sets `--network bridge` + `HTTP(S)_PROXY` and no packet filter exists — the proxy is the only *sanctioned* way out, not a hard network wall; metrics refreshed everywhere (120 packages, 375 source + 406 test files, ~89.8K non-test LOC); the six `NILCORE_OPENROUTER_*` / `NILCORE_COMPAT_*` vars documented only in suffix shorthand are now spelled out; `docs/REFERENCE.md` gained an explicit upgrade note for the two settings whose MEANING changed at `573a4df` (`NILCORE_SELFIMPROVE_AUTOAPPROVE=1` was a no-op and now really merges; swarm shards now reach their preset's declared hosts). Roadmap/plan docs keep their plans but now carry accurate SHIPPED / PARTIALLY SHIPPED / NOT-BUILT status and no longer name pruned symbols as live. `CHANGELOG.md` history untouched (append-only). Markdown only — no `.go`, `Makefile` or CI file changed. _Owns:_ `*.md`, `docs/**`. _(Phase 16 — documentation)_

- **chore(features-review): reconcile shipped-but-inert subsystems, then a full docs pass over the assembled diff.** A features-review sweep found a cluster of built-but-silently-dead paths (several of which reported false green), fixed each with a discriminating test, and then reconciled the docs to the code as it now is. **Verifier integrity:** the `vcache` key now folds in the browser-verify command, `NILCORE_VERIFY_PACKS`, `NILCORE_EVIDENCE_VERIFY`, `NILCORE_EVIDENCE_MAX_AGE` and the sandbox image identity (a behavioral toggle can no longer serve a stale green), and the worktree content hash includes `.nilcore/artifacts` when evidence verification is on; evidence verifiers are now discovered **lazily at Check time** on run/chat/serve/resume (they were built eagerly and so never ran); tiered verify reads only the `go test` leg of a compound command and replicates `-run`/`-skip`; a new boot `validateVerifyEnv` exits 2 on a bad `NILCORE_VERIFY_PACKS`/`NILCORE_EVIDENCE_MAX_AGE`. **Artifact packs:** `code.test_passes` ran `go test -- '<pkg>'` (which never tested the selected package — a false green) → `go test '<pkg>'`; `code.build_passes` on an undetectable layout is now Unverifiable, not Pass; `schema_verify` events are actually emitted (the report's SchemaDefects section was permanently empty; `DefectMeta` gained `claim_id`/`reason` + json tags); `software.github_tag_exists` paginates; the audit pack rejects leading-dash patterns. **Swarm:** shards now actually receive their derived egress (an allowlist proxy is stood up and applied per shard box — previously every shard ran `--network none`, so the research preset could never verify green and `--egress-allow` was inert); `--resume` re-seeds skipped/queued/running shards (planned-DAG dependents were being dropped — a false green) and red budget-exhausted shards block a clean converge. **Graduated auto-approval (graapprove) was structurally unreachable:** `AllowBranches:["*"]` never matched slash-y branches (`path.Match`) and trust/rate keyed on per-run-unique scopes — now `*` matches any non-empty scope, and trust + the per-day rate window key on a stable scope **family** (`task/trig-123`→`task/*`, a bare sha→`#commit`); the protected-base floor (main/master/release/trunk/stable/prod*) and `DenyBranches` stay on the **concrete** branch; an empty scope never auto-approves. **Self-improve now really merges:** it had no merge step (it logged `self_edit_merged` and returned `merged=true` while nothing landed) — there is now a real `Flow.Merge` seam, `Run` returns the verified branch, `merged=true` means it actually landed, and new event kinds record the failure modes (`self_edit_merge_unwired`/`self_edit_no_branch`/`self_edit_merge_failed`). **Session / front door:** drive-terminal persist ran on an already-cancelled ctx so conversation state never wrote (now detached — `context.WithoutCancel` + 5s timeout); `Session.Checkpoint` (previously zero callers) is now called on chat exit and in serve's drainShutdown; steer markers (`!`/`/steer`) are stripped before the model sees the turn; memory/lessons (`MemoryContext`) and operator steering (`SteeringContext`) now reach chat/TUI and serve, not just run/watch; `-blast-radius` now fences chat and serve native drives (chat mints ONE shared blast budget for its proxy + sandboxes + gate; a serve-restart-resumed run gets the dollar ceiling); serve's `--webhook` no longer opens a second SQLite handle on the DB serve owns; the serve-embedded flywheel gate is deny-default (was bound to `os.Stdin`); a failed serve drive no longer renders "✓ not verified" over Telegram/Slack. **Security / robustness:** `browsersession.Observation.URL` is scrubbed for typed secrets (an I3 leak into the model prompt AND the append-only log); browseragent irreversible-signal, `router.Classify`, `trust.Classify` and `session.referencesGoal` moved from substring to word-boundary matching; Slack model text is escaped (was `<!channel>`-injectable), evidence-rich gates clip to Slack's 3000-char cap instead of silently auto-denying, 429/Retry-After is honored, and ws frame length is sanity-checked; Telegram scrubs the token from transport errors and clips long messages; MCP stdio writes are ctx-cancellable (a deadlock), responses size-capped, children reaped, stale resource/prompt wrappers pruned; one malformed SKILL.md no longer disables ALL skills; codeintel + live walks skip symlinks (I4) and vendor dirs (node_modules/vendor/.venv/dist/build), and several tool reads moved to `O_NOFOLLOW`; the termui spinner stop/animate deadlock is fixed (TryLock). **Backends / providers:** an empty-content native reply no longer poisons history and kills the run; Anthropic — a zero-frame clean-EOF stream is a retryable error, server-tool-only (`pause_turn`) turns are preserved, all beta headers collected; OpenAI — web plugin deduped, tool_result error signalled, max_tokens splice fixed; codex/claude-code `digText` matches the real claude-code stream-json (`result` key, `message.content[]`). **Also:** experience projection is rotation-aware (`Rebuild` drops stale keys); autosrc no longer kills a source permanently on a full queue; emit neutralizes ANSI in gate evidence; integrate stops on a failed `merge --abort`; the eventlog torn-tail heal won't drop committed lines; the report writer error-lists as json. **Docs reconciled to the code:** restored the AGENTS.md backend set (a find/replace had corrupted it to "Codex, Codex"), corrected Go 1.25 + dropped the never-adopted `sqlc` in PREREQUISITES, purged dead-symbol references (`impact.Localize`/SBFL, `policy.Gate`/`EgressWith`, `internal/blackboard`, `emit.NopEmitter`, `objective.MarkRun`), corrected the phantom `NILCORE_BROWSER_AGENT`/`NILCORE_DESKTOP_DARWIN` gates to the shipped gating, documented the previously-undocumented env vars in `docs/REFERENCE.md`, refreshed the stale REFERENCE.md metrics/CLI/package inventory, and stated the true Phase-15 status (web search shipped; the provider-compat eval P15-T13 was never built). _Owns:_ repo-wide — `internal/*`, `cmd/nilcore/*`, `AGENTS.md`, `CHANGELOG.md`, `README.md`, `STATE.md`, `docs/*`. _(features review)_
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ NilCore borrows intelligence instead of re‑encoding it — so the whole agent

[![CI](https://github.com/RNT56/NilCore/actions/workflows/ci.yml/badge.svg)](https://github.com/RNT56/NilCore/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/RNT56/NilCore?label=release&color=6f42c1)](https://github.com/RNT56/NilCore/releases/latest)
[![Go](https://img.shields.io/badge/Go-1.25-00ADD8?logo=go&logoColor=white)](go.mod)
[![Go](https://img.shields.io/badge/Go-1.25.12-00ADD8?logo=go&logoColor=white)](go.mod)
[![Dependencies](https://img.shields.io/badge/dependencies-SQLite%20%2B%20x%2Fsys-2ea44f)](go.mod)
[![Agent size](https://img.shields.io/badge/agent-~90k%20LOC-1f6feb)](#the-receipts)
[![Sandboxed](https://img.shields.io/badge/model%20execution-sandboxed-2ea44f)](#the-seven-invariants-non-negotiable)
Expand Down Expand Up @@ -136,7 +136,7 @@ A `NILCORE.md` / `AGENTS.md` steering file loads as trusted project instructions

## Quickstart

**Requires** Go 1.25+. On Linux with a Landlock‑capable kernel (5.13+) and unprivileged user namespaces, NilCore sandboxes the loop with **no container runtime at all** — the auto‑detected host‑native namespace backend. Otherwise (or with `-sandbox container`) it uses a container runtime (`podman` rootless preferred, or `docker`).
**Requires** Go 1.25.12+. On Linux with a Landlock‑capable kernel (5.13+) and unprivileged user namespaces, NilCore sandboxes the loop with **no container runtime at all** — the auto‑detected host‑native namespace backend. Otherwise (or with `-sandbox container`) it uses a container runtime (`podman` rootless preferred, or `docker`).

```sh
# Install (or grab a binary from Releases)
Expand Down
10 changes: 5 additions & 5 deletions STATE.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# STATE.md — NilCore state snapshot

> **Point-in-time snapshot, 2026-07-10** (HEAD `573a4df`, on branch
> `claude/nilcore-features-review-931610`; **unmerged** — the features-completeness
> review + remediation pass). This is a derived report, not a contract. The sources of
> truth remain `CLAUDE.md` (constitution), `docs/ARCHITECTURE.md` (technical law +
> **Point-in-time snapshot, 2026-07-12** (`origin/main` after the merged
> features-completeness review #101 and adversarial remediation #100). This is a
> derived report, not a contract. The sources of truth remain the mirrored
> `AGENTS.md` / `CLAUDE.md` constitutions, `docs/ARCHITECTURE.md` (technical law +
> invariants), and `CHANGELOG.md` (the ledger). Where this file and those disagree,
> **they win** — regenerate this one.

Expand Down Expand Up @@ -31,7 +31,7 @@ the audit trail.
| Go packages | **120** total (`go list ./...`); **111** under `internal/` |
| Direct module deps | **3 sanctioned families** (5 direct requires) — `modernc.org/sqlite`, `golang.org/x/sys`, Charm TUI (`bubbletea`/`bubbles`/`lipgloss`, behind `//go:build tui`) |
| `CGO_ENABLED` | **0** (pure-Go; cross-compiles cleanly) |
| Gate | `make verify` (build + vet + lint + test) — green. (`-race` + the `tui` build are separate lanes, not folded in.) |
| Gate | `make verify` (build + vet + lint + test) — green. Race, TUI, sandbox, browser E2E, `govulncheck`, and installer-integrity proofs are separate CI lanes. |

---

Expand Down
2 changes: 1 addition & 1 deletion docs/PREREQUISITES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Everything you need to build, run, and contribute to NilCore. Source of truth fo

| Tool | Version | Why |
|---|---|---|
| Go | 1.25+ (matches the `go` directive in `go.mod`) | the entire core |
| Go | 1.25.12+ (matches the patched `go` directive in `go.mod`) | the entire core; older 1.25.x releases contain reachable standard-library vulnerabilities |
| Container runtime | **Podman ≥ 4 (rootless, preferred)** or Docker | the sandbox |
| git | ≥ 2.30 | worktree-per-task workflow |
| make | any | `make verify` is the gate |
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module nilcore

go 1.25.0
go 1.25.12

require (
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
golang.org/x/sys v0.42.0
golang.org/x/sys v0.44.0
modernc.org/sqlite v1.52.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
Expand Down
15 changes: 13 additions & 2 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
#
# curl -fsSL https://raw.githubusercontent.com/RNT56/NilCore/main/scripts/install.sh | sh
#
# Override with: NILCORE_VERSION=v0.2.0 NILCORE_BINDIR=$HOME/.local/bin sh install.sh
# Override with: NILCORE_VERSION=v1.2.0 NILCORE_BINDIR=$HOME/.local/bin sh install.sh
# Mirrors/tests may set NILCORE_RELEASE_BASE to a trusted directory or HTTPS base URL.
set -eu

REPO="RNT56/NilCore"
BINDIR="${NILCORE_BINDIR:-/usr/local/bin}"
VERSION="${NILCORE_VERSION:-latest}"
RELEASE_BASE="${NILCORE_RELEASE_BASE:-}"

os="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$os" in
Expand All @@ -26,7 +28,9 @@ case "$arch" in
esac

asset="nilcore-${os}-${arch}"
if [ "$VERSION" = "latest" ]; then
if [ -n "$RELEASE_BASE" ]; then
base="${RELEASE_BASE%/}"
elif [ "$VERSION" = "latest" ]; then
base="https://github.com/${REPO}/releases/latest/download"
else
base="https://github.com/${REPO}/releases/download/${VERSION}"
Expand All @@ -52,6 +56,13 @@ curl -fsSL "$url" -o "$tmp"
# FAIL CLOSED rather than install unverified.
if curl -fsSL "$sumurl" -o "$sumtmp"; then
expected="$(awk '{print $1}' "$sumtmp" | head -n1)"
if [ "${#expected}" -ne 64 ]; then
echo "error: invalid SHA-256 record for ${asset}" >&2
exit 1
fi
case "$expected" in
*[!0-9a-fA-F]*) echo "error: invalid SHA-256 record for ${asset}" >&2; exit 1 ;;
esac
if command -v sha256sum >/dev/null 2>&1; then
actual="$(sha256sum "$tmp" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
Expand Down
34 changes: 34 additions & 0 deletions scripts/verify-release-assets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh
# Fail-closed release-set verifier shared by the release workflow and its local E2E.
# A publishable NilCore release is exactly four non-empty platform binaries, one valid
# per-binary SHA-256 record each, and a freshly-derived four-line SHA256SUMS manifest.
set -eu

dist="${1:-dist}"
assets="nilcore-darwin-amd64 nilcore-darwin-arm64 nilcore-linux-amd64 nilcore-linux-arm64"

if command -v sha256sum >/dev/null 2>&1; then
check_record() { sha256sum -c "$1"; }
write_manifest() { sha256sum $assets > SHA256SUMS; }
elif command -v shasum >/dev/null 2>&1; then
check_record() { shasum -a 256 -c "$1"; }
write_manifest() { shasum -a 256 $assets > SHA256SUMS; }
else
echo "error: no SHA-256 tool (sha256sum/shasum)" >&2
exit 1
fi

for asset in $assets; do
test -s "$dist/$asset" || { echo "error: missing release asset $asset" >&2; exit 1; }
test -s "$dist/$asset.sha256" || { echo "error: missing checksum $asset.sha256" >&2; exit 1; }
( cd "$dist" && check_record "$asset.sha256" )
done

( cd "$dist" && write_manifest )
test "$(wc -l < "$dist/SHA256SUMS" | tr -d ' ')" = "4" || {
echo "error: SHA256SUMS must contain exactly four records" >&2
exit 1
}

echo "--- SHA256SUMS ---"
cat "$dist/SHA256SUMS"
Loading
Loading