v0.106.0 #732
v0.106.0
#732
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
What
The authority release. Two flag-days land in the same window, and both
change what an existing file MEANS. The envelope's value forms split into the
four authorities —
vars:andenv:are dead,inputs:·config:·const:·secrets:are the whole family — and apermits:block stopsbeing optional: absent is now the EMPTY boundary, not the unconfined floor.
Around them the attestation lane arrives whole: a run declares its entropy and
its clock, boots from a manifest, seals what it covered, signs, anchors to a
public transparency log, and exports a pack an auditor reads without trusting
us. This is the first release where a workflow written yesterday can refuse to
check today. The migration is the front page, not a footnote —
nika check --fixapplies the mechanical half and STOPS untouched rather than guess atthe rest.
1 · The value authorities —
vars:andenv:are dead (R3a « the E-split » · #669)The law is classify-not-rename: every old entry moves to the authority its
ROLE commands, never to one bulk destination.
vars:entry · a typed parameter a caller suppliesinputs:(typed ·required:·default:)NIKA-VALUES-001vars:entry · a fixed value baked into the fileconst:(bare literal, or{ type, value })NIKA-VALUES-001env:entry · non-sensitive runtime configurationconfig:(typed · deployment-supplied)NIKA-VALUES-002env:entry · a credentialsecrets:(a store reference · never a literal)NIKA-VALUES-002env:entry · a name a child process must seepermits: { env: [NAME] }(exact names)NIKA-VALUES-002${{ vars.X }}·${{ env.X }}-001·-002${{ anything_else.X }}NIKA-VALUES-003nika check --fixmigrates thevars:half mechanically — comment-preserving,idempotent, and class-aware (the name→class map comes from the file's own
block, never a blind rename). It leaves the file UNTOUCHED and names the reason
on a credential-shaped name, a typed-only declaration, a flow-style
vars: {…}header, arequired: trueentry withouttype:, or an emptyblock: atomic-or-nothing, the codemod never guesses.
env:has nomechanical repair — re-shaping a flat string map into typed
config:declarations is a human classification, and the teaching says so at the point
of refusal.
Two consequences worth reading twice ·
config:resolves ONLY against thedeclared block, so the engine never silently falls back to the OS environment
(every value a workflow depends on is visible in the file); and
--varnownames
inputs:— the flag keeps its spelling, its target moved.2 · The boundary — absent
permits:is zero authority (F-O8 · NEP-0003 · #691)A missing
permits:block used to be the unconfined floor. It is now theEMPTY boundary: a body carrying any effect with no block refuses
NIKA-AUTH-006at check — before a token is spent — and the runtime gatesrefuse before any spawn. Only a pure-compute body stays clean, and it gets a
hint teaching
permits: {}, the legal zero. The 39 embedded workflows thatshipped with this repo — examples, showcase tiers, templates, battery files —
each had to declare its inferred block: that is the blast radius, measured
rather than guessed. The repair is one command —
nika check --infer-permitsprints the tightest block, and the round-trip lawholds (the inferred block re-checks clean).
Four more refusals ride the same window. Each was previously admitted:
NIKA-AUTH-007NIKA-AUTH-008NIKA-AUTH-009permits: env:entry naming a dangerous-floor variable — the engine strips it unconditionally, so the grant is an inert dead grantNIKA-AUTH-010*.example.cominpermits.net.http— the*.subdomain wildcard delegates the boundary to the zone operator (every host under the suffix, present and future). Name exact hosts, or the bare*when allow-all is genuinely intendedNIKA-SEC-008nika:fetchwhose resolved URL path names a code-bearing class with noinert:door declared — the read hides an execution sink (NEP-0006)And one law that carries no code but changes what a child process SEES · a
spawned child no longer inherits the engine environment. Its environment is
COMPOSED from a cleared slate — the runner floor ∪ the names declared in
permits: env:∪ the task's ownenv:map, the dangerous names stripped last(F-O4 · NEP-0005). A workflow that relied on an ambient variable reaching an
exec:child must now name it.3 · The embedder cut —
nika-schema→nika-check(#683 · no re-export shims)nika_schema::{analyze, AnalyzedWorkflow}nika_check::{analyze, AnalyzedWorkflow}nika_schema::{check, check_composed, infer_permits}nika_check::{…}nika_vocab::VarType(the flat 6-enum)TypeExprof spec 09NIKA-PARSE-019(entropy × clock)NIKA-PARSE-026·-027·-028NIKA-PARSE-015(malformed typedvars:)TypeExpr; what stays outside the grammar refusesNIKA-TYPE-001nika-schemakeeps its blueprint shape (THE PARSER: AST + raw + error +keysets). Every consumer inside this repo — cli · lsp · runtime · dap · mcp ·
verb-agent · lints · graph · display · onboard · the fuzz harness — already
points at
nika_check; what migrates is the embedding code outside this repo.The cut is clean by choice: a shim would have kept two names for one judgment.
The retired and re-minted codes count as breaking for anyone FILTERING on
them — a tool watching
NIKA-PARSE-019for the entropy × clock contradictionwill never see it pass again.
4 · The predicates and the type grammar (R5 · #677 · R3b · #673)
after: { t: succeeded }after: { t: success }NIKA-DAG-005after: { t: failed }after: { t: failure }NIKA-DAG-005type: booleantype: bool— the one boolean spelling, no aliasNIKA-TYPE-001type: array·type: object{ array: T }·{ object: { … } }NIKA-TYPE-001default:that does not fit its owntype:NIKA-DEFAULT-001skippedandterminalare unchanged.nika check --fixapplies thepredicate respelling 1:1 — flow and block
after:forms, quoted values,comments preserved, idempotent; a
when:status comparison is theNIKA-DAG-007class and is never touched.NIKA-DEFAULT-001closes a realsoundness hole: a declared default that could never satisfy its own type used
to sail through.
Added
nika-check— the static judgment crate (new L0 member · split fromnika-schemaat the 15k crate-size wall; the nika-graph/nika-dapprecedents): the workflow analyzer (
analyze·AnalyzedWorkflow· the ONEderived-DAG-edge computation every surface projects) plus the whole
nika checkladder (check·check_composed· cost ceiling · secret-leak IFC ·capability-escape fit · trifecta · policy · gate reachability · the
RunCertificate·infer_permits). Static judgment without the CLI, forthe embedder/SDK surface (§Migration 3 for the import map).
run:— the run declares its entropy and its clock (F-P3 · NEP-0010 ·feat(schema,runtime): the run declares its entropy and clock (F-P3) #711) — a new envelope block with two keys:
entropy:(ambient·none·{ seeded: <n> }) andclock:(system·virtual). A declared pair thatcontradicts itself refuses at parse (
NIKA-PARSE-026ambient × virtual ·NIKA-PARSE-027none|seeded × system), and anentropy: nonethatnevertheless consumes a structural randomness source refuses at check
(
NIKA-PARSE-028).ambientis the default whenrun:is absent — thehonest status quo, so nothing that ran before changes. Determinism becomes a
declaration the engine holds you to, not a hope.
The run's lifecycle is attested (F-P2 · NEP-0011 draft · feat(runtime,dap,cli): the run lifecycle is attested (F-P2) #718) — the
workflow_startedprologue becomes a boot manifest (spec_pin·stamper_kind· the resolvedclock·seedunder a determinism demand);the run seal's
coversextends additively with the foldedreceipt_digest,the consumed budgets and the exercised effects (the classic four-field seal
stays byte-identical); a journal that never reached a lifecycle-terminal
frame verifies INCOMPLETE — the verifier's finding, never the dying
run's silence; and a check report stamped with a different semantic hash
than the booting workflow refuses before the first event (the
judged-vs-booted binding · exit 2 · semantic grain).
The receipt fortress — bounds are code (F-P1 · NEP-0012 draft · feat(nika-dap): the receipt fortress — bounds are code (F-P1) #721) —
the verifier is the one component guaranteed to parse attacker-supplied
bytes, so its bounds became named constants on every profile: 1 MiB per
document, a string-aware pre-parse depth scan at 32, proof-bearing arrays at
64, identifier strings at 256. The chain walk refuses an over-long line
BEFORE the parse (the new
LineOverLongverdict, rendered as a FILE refusalby
trace verifyand an honest refusal by the evidence pack), and theanchor sidecar loads through the same door.
nika sign·nika key·run --require-signature(S3 · feat(nika-cli): nika sign + run --require-signature — workflow author-binding (S3) #660 · feat(nika-cli): signed run seals — run_sealed event + run-key custody #655) —workflow author-binding.
nika sign <file>mints<file>.minisig(
--checkverifies);nika keyis the run-signing key lifecycle (mint ·TOFU fingerprint · rotate — old public halves stay verifiable);
nika run --require-signaturerefuses an unsigned or invalidly-signed workflow atexit 2. Runs emit a signed
run_sealedevent under run-key custody.nika trace anchor(feat(nika-cli): nika trace anchor (Rekor v2 + RFC 3161) + tiered trace verify (OK/SEALED/ANCHORED/REPLAYED) #665) — notarize the journal head OUTSIDE thejournal: the post-seal head, signed with the run key, submitted to the
public Sigstore Rekor v2 transparency log plus an RFC 3161 timestamp,
writing a detached
<trace>.anchor.jsonsidecar. An explicit NETWORK act —this verb IS the opt-in.
nika trace verifynow climbs a four-tier ladderand reports the highest honestly-attained tier: chain OK · SEALED (the
run_sealedsignature verifies against a custody key) · ANCHORED (thesidecar verifies fully offline) · REPLAYED (
--replaycompares a freshrun; verify never re-executes).
nika evidence(feat(nika-cli): the evidence pack — nika evidence exports what an auditor needs #662) — export the evidence pack for one run: journal +manifest + receipt + a
VERIFY.mdthat tells an auditor what to run.The permit-decision witness (F-O6 · NEP-0007 · feat(runtime+cli): the permit witness · trace verify finding · the check-run equivalence oracle (F-O6) #701) — every permit
decision is recorded in the journal, granted and refused alike, so
trace verifycan judge the boundary a run actually rode; and thecheck ⇔ run equivalence oracle proves the two agree.
trace_formatstays 2(the witness rides existing frames · no wire bump).
MCP tool pinning — TOFU + fail-closed drift (feat(nika-mcp): tool pinning — TOFU + fail-closed drift (anti-rug-pull) #657) — a configured MCP
server that changes its tool definitions after you approved them is the rug
pull. Per-tool pins (blake3 over a domain-separated canonical pre-image)
land in
.nika/mcp_pins.json(mcp_pins_format: 1) beside a reviewablesnapshot; first contact enrolls loudly, a match proceeds silently, ANY drift
fails closed with a diff naming the CHANGED field and returns no tools. A
hand-edited lockfile is
NIKA-MCP-004, never a silent re-TOFU.nika mcp approve <server>re-pins after human review.The exec sandbox is wired (ADR-095 L6 · feat(nika-runtime): the exec sandbox is wired — permits: jails the child (ADR-095 L6) #642) —
permits:jails thechild (seatbelt on macOS · landlock/bubblewrap on Linux), the
SandboxSpecnetwork arm becomes a tri-state (feat(nika-kernel-core): SandboxSpec network tri-state — deny / allow / allowlist (C11 part 1) #658), MCP stdio servers arespawned inside the same confinement (security(nika-mcp): confine spawned stdio servers in the exec OS sandbox #667), and the loopback egress
proxy makes the sandbox's network arm the exact projection of
permits.net.http(feat(nika-exec-runner): the loopback egress proxy — sandbox allowlist arm enforced (C11 part 2, srt model) #663 · feat(exec-runner,check): the egress proxy is the exact projection of net.http (F-P5) #706 · F-P5 · NEP-0008).Two new task fields —
inert:(the declared door for anika:fetchwhose payload is data, never code · the
NIKA-SEC-008sanction) anddeclassify:(from·to·because· the audited secret-flowsanction).
Registry client v0.2 signatures (feat(nika-registry-client): v0.2 signatures — minisign + TOFU (NIKA-REG-006/007) #648) — minisign + TOFU on registry
pulls (
NIKA-REG-006·NIKA-REG-007).NIKA-DRIFT-001— declared-but-unused hints incheck(feat(nika-cli): declared-vs-used drift hints in check — NIKA-DRIFT-001 #661) — adeclared name or a
permits:entry nothing in the body references. Advisory:it never fails the audit (the reverse direction, used-but-undeclared, is the
hard refusal surface).
Two new journal event kinds —
declassifyandrun_sealed. Additive:trace_formatstays 2, but a consumer matching the kind vocabularyexhaustively must learn them.
Spec 17 · trace — the journal dialect becomes normative in the embedded
pack (the NDJSON frame grammar chained by sha256, the prologue manifest, the
closed kind vocabulary), and the pack ships the
law+registriesJSONschemas.
Changed
run:declared-pair refusals ride their dedicated mints — theparse-level entropy × clock contradictions stamp
NIKA-PARSE-026(ambient ×virtual) and
NIKA-PARSE-027(none | seeded × system), and the check-sideentropy: none× structural-source judgment stampsNIKA-PARSE-028— wasthe registered generic
NIKA-PARSE-019; the NEP-0010 mints landed with the87f764a spec pack resync.
The lethal-trifecta judge asks a better question (trifecta v2.0 ·
NEP-0002 · feat(nika-schema): trifecta v2.0 — the realized-flow judge (NEP-0002) #643) — v1.1 asked « could this workflow complete the trifecta? »
over the declared capability set. v2.0 asks « does untrusted content
actually REACH an egress? », the integrity half of the flow lattice. The
inversion that matters: an
infer:/agent:output carries the taint whenits prompt saw it — a summary of an attacker's page carries the payload.
v2.0 findings ⊆ v1.1, so nothing that passed before starts failing.
permits.toolsis enforced at run (fix(nika-runtime): permits.tools enforced at run — the last check-only axis closes #639) — the last check-only axiscloses. A tool call outside the boundary now refuses at dispatch, not only
at check.
NIKA-SEC-005is core-visible — law 5 at every tier (fix(check): nika-sec-005 is core-visible — law 5 at every tier #708) — thenet-egress floor was already emitted by
check; only the core-tierconformance filter hid it. The core verdict now matches the reference
oracle.
nika trace verifyexit 0 means « the highest attained tier holds » —the verdict is tiered, not binary (exit 2 broken or forged · exit 3
unchained or a missing input).
The embedded spec pack catches up to the spec branch — NEP-0004 through
NEP-0012 land in the pack
nika specserves, along with the 6-namespacesubstitution family, the 25 error namespaces / 96 error codes, and the
17-provider catalog count.
Fixed
A planted symlink can no longer pivot an
fs:grant (H2 · NEP-0009 ·feat(nika-runtime): the fs permit names an effective path identity (H2 · NEP-0009) #710 · the CVE-2024-42472 class) — the mount-projection arm followed a
symlinked bind SOURCE where the kernel path-walk arm refuses at open, so an
upstream task that replaced
/ws/datawith a link to$HOME/.sshmade alater task's
fs.read: [/ws/data]bind the wrong tree. Every grant'sliteral prefix is now re-judged as its EFFECTIVE path identity at dispatch,
before the jail is built: a legitimately-symlinked ANCESTOR is absorbed, the
final component stays lexical, and an identity that redirects outside the
judged path is refused before spawn (
NIKA-SEC-004) — never mounted underthe judged name, never rewritten to the resolved form. The receipt does not
lie.
A forged
CheckReportcannot buy authority (fix(nika-runtime): the runtime re-derives the boundary subset — a forged CheckReport cannot pass #656 ·NIKA-1707) — theruntime re-derives the boundary subset at run start; a clean report over
different bytes is not clean.
Resolved secrets are redacted from the journal, and journals are 0600
(fix(nika-runtime): resolved secrets are redacted from the journal · journals 0600 #640).
Pulled model weights are sha256-verified against the Hub's declaration
(fix(nika-models): pulled weights are sha256-verified against the Hub's declaration #641).
Security-boundary refusals never feed back to the model (fix(nika-verb-agent): security-boundary refusals never feed back to the model #638 ·
NIKA-468) — a mid-loop refusal is the boundary's word, not another turn'scontext.
A
required: trueinput with neither adefault:nor a--varrefusesat admission (fix(nika-runtime,nika-cli): the required-input admission preflight — refuse before the DAG #674 ·
NIKA-1708) — before the DAG spends a task, notmid-run.
Key trust reads the public half only (fix(nika-dap): key trust reads the public half only — no decrypt on the print path #668) — no decrypt on the print
path.
The red team's residuals close (fix(cap,schema): close the 4 edge cases the red team found #702 · fix(exec-runner,cap): close 3 residuals the red team found (rebinding · port floor · suffixes) #703 · fix(cap,runtime): the final review's two catches (decode-then-trim + finally attestation) #704) — four permit edge
cases, three exec-runner residuals, decode-then-trim, and the
finallyattestation.
The taught flow bindings quote their islands (fix(agents): the taught flow bindings quote their islands #717) — an unquoted
${{ }}in a YAML flow mapping isNIKA-PARSE-001; the shipped teachingsurfaces no longer hand it to you.
Install
Tarballs below: macOS arm64 / x64 · Linux x64 / arm64, plus
SHA256SUMS.Verify: three independent proofs
Provenance
Built from tag
v0.106.0byrelease.ymlon GitHub-hosted runners. Provenance is published twice: GitHub's native
build attestation (proof 2) and the SLSA generator's
multiple.intoto.jsonlrelease asset (proof 3). The release itself is a claim on the
machine-verified timeline: https://nika.sh/timeline
What's Changed
Full Changelog: v0.105.0...v0.106.0
This discussion was created from the release v0.106.0.
All reactions