AGPL-3.0-or-later · forever.

the path · 11

The lift — the one door through the taint.

run itnika try 11-declassify-the-door

11-declassify-the-door.nika.yamlsource
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## 11 · The lift — the one door through the taint.## A permit-relevant argument fed by an UNTRUSTED value (`inputs.*` ·# `config.*` · anything fetched) is the parameterisation taint. When the# check can resolve the value and it ESCAPES the bound, it refuses with# `NIKA-AUTH-008` — and the reflex that refusal provokes (widen the# boundary until the message stops) is the wrong door. The right one is# authored: a `lift:` entry naming the **taint** law on the task, with a# reviewable `because:`# (spec/10-authority.md · "The only door is the authored one").## Demonstrates · every claim below was run against this exact file#   - the wall · flip this file's `default:` to "../outside/secrets.csv"#     and the check refuses ✖ NIKA-AUTH-008; the door below turns that#     SAME variant check-green — the static question is deferred, not#     answered#   - never a bypass · that deferred variant still dies `NIKA-SEC-004` at#     run, and `--var path=README.md` on THIS file dies the same way — the#     boundary confines every value, door or no door#     (spec/10-authority.md · "It is never a permit bypass")#   - `lift:` · task-level · `law:` a closed enum (`taint` here) · `from:`#     a dotted binding, required by `taint` and forbidden elsewhere ·#     `because:` non-empty, the review record#   - what the door yields on an ADMITTED value · the vouching lands in#     the signed trace — one `lift` frame per entry, holding the#     `because:`#   - a wall with no door at all · a permit BOUND can never interpolate#     (`NIKA-AUTH-007` · a bound is a literal you can read)## Run · nika run examples/11-declassify-the-door.nika.yaml#   another vendor file · --var path=examples/fixtures/orders.csvnika: declassify-the-doorpermits:  tools: ["nika:jq", "nika:read"]  fs:    # The bound is the DOMAIN the operator grants: any fixture csv, one    # path segment, nothing nested. `*` never crosses `/` — a caller aiming    # at `fixtures/deep/x.csv` or `../secrets.csv` is refused by the    # boundary itself, before the taint question even comes up.    read: ["examples/fixtures/*.csv"]inputs:  path:    type: string    default: "examples/fixtures/inventory-q3.csv"    required: true    description: "WHICH vendor csv to load · must sit inside the fs.read bound"tasks:  load:    invoke:      tool: "nika:read"      args:        path: "${{ inputs.path }}"    # The door. `law:` names WHICH law this entry lifts; `from:` names the    # exact tainted binding; `because:` is the sentence a reviewer holds    # you to. What it buys, measured: a resolvable ESCAPING value stops    # refusing at check (the question is handed to the run), and every run    # writes one `lift` frame into the signed trace carrying this exact    # sentence. What it does not buy, measured: the boundary — an    # out-of-bound path dies `NIKA-SEC-004` at run with the door in place.    lift:      - law: taint        from: inputs.path        because: "the caller picks WHICH fixture csv, never an arbitrary path — the one-segment *.csv bound confines every value this input can take, and the run refuses anything outside it"  shape:    with:      raw: ${{ tasks.load.output }}    invoke:      tool: "nika:jq"      args:        input: "${{ with.raw }}"        expression: '{rows: (split("\n") | map(select(. != "")) | length - 1)}'outputs:  summary:    value: ${{ tasks.shape.output }}    description: "How many data rows the vendor file carries (header excluded)"

nika-spec@2b3d6ac3e · sha256 b18bdb82cab04c5e