Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ab09786
fix: stop repo gitignore hiding discovered source
Jun 21, 2026
e73124b
fix: prevent stdlib submodule spoof taints
Jun 21, 2026
c0de30c
fix: reject MCP doctor caller filigree URLs
Jun 21, 2026
7d16b55
fix: harden rekey snapshot provenance
Jun 21, 2026
b1a9de3
fix: preserve rust command shadow rebinds
Jun 21, 2026
0a30f62
fix: drop untrusted MCP sibling URLs
Jun 21, 2026
d7ecb90
fix: bound delta caller seed expansion
Jun 21, 2026
09eae7a
fix: preserve explicit sibling loopback pins
Jun 21, 2026
cbd287d
fix: gate waiver_add entity_symbol resolve under MCP network policy
Jun 22, 2026
6d9172c
fix: eliminate cubic candidate-set merge blowup (scan DoS)
Jun 22, 2026
b5bdb7d
docs: consolidate Wardline release materials
Jun 23, 2026
f6cf5e7
fix(doctor): preserve filigree probe provenance
Jun 23, 2026
bc96534
fix(filigree): soft-fail unsafe mint token reads
Jun 23, 2026
eff7c7c
fix(scanner): bound l2 taint analysis work
Jun 23, 2026
5113b44
fix(filigree): avoid response body amplification
Jun 24, 2026
42e2bab
fix(site): pin site-kit fetch in pages build
Jun 24, 2026
e27ed1b
fix(mcp): advertise scan integration effects
Jun 24, 2026
de1509d
fix(mcp): gate rekey cache-dir writes
Jun 24, 2026
9747831
fix(rust): isolate mount overlay prepass failures
Jun 24, 2026
751af16
fix(fingerprint): bind singleton suppressions to source body
Jun 24, 2026
3e1db6b
fix(rust): surface invalid trust markers
Jun 24, 2026
5b30a2f
fix(fingerprint): include full call spans
Jun 24, 2026
e4e4b20
fix(gitignore): track wardline suppression state
Jun 24, 2026
27ec91e
fix(doctor): preserve resolved url provenance
Jun 24, 2026
eca642c
fix(scan): guard explicit output paths
Jun 24, 2026
f42875d
fix(scan): preserve raw hashes for CRLF files
Jun 24, 2026
1e3e0d6
fix(filigree): require confirmed legacy identity
Jun 24, 2026
a0d28f6
fix(rust): tolerate invalid UTF-8 manifests
Jun 24, 2026
edf852d
fix(install): repair mismatched filigree loopback urls
Jun 24, 2026
8f603b3
fix(rust): track args and captured format taint
Jun 24, 2026
31e03a4
fix(rust): normalize nested cfg predicates
Jun 24, 2026
4fbe585
fix(rust): avoid recursive inline module indexing
Jun 24, 2026
84f01cf
fix(discovery): keep nested Rust target modules
Jun 24, 2026
9638114
fix(install): avoid quadratic foreign fence scan
Jun 24, 2026
5924d7f
fix(filigree): redact diagnostic URLs
Jun 24, 2026
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
7 changes: 6 additions & 1 deletion .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# copied verbatim into the build output). It consumes the shared @weft/site-kit,
# which lives in a SUBDIRECTORY of a DIFFERENT repo (foundryside-dev/weft).
# npm cannot install a git subdirectory as a file: dep directly, so the build
# sparse-fetches packages/site-kit into site/vendor/site-kit first
# sparse-fetches a pinned packages/site-kit commit into site/vendor/site-kit first
# (scripts/fetch-site-kit.mjs), then `npm install` resolves the file: dep and
# `astro build` compiles it. The fetch also runs as a preinstall hook, but the
# explicit step keeps the order legible.
Expand All @@ -29,6 +29,11 @@ concurrency:
group: pages
cancel-in-progress: false

env:
# Privileged Pages builds must consume an immutable site-kit revision. Update
# this SHA deliberately when promoting a new foundryside-dev/weft site kit.
WEFT_SITE_KIT_REF: a8f9a6a77458d2ec697cfbc1f71dd88a51962cb7

jobs:
build:
runs-on: ubuntu-latest
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ output/
# port (a live, never-committed runtime artifact, not tracked state).
.weft/*/ephemeral.port

# Local sibling tool stores are runtime/tooling state for this checkout. Keep
# wardline's own .weft/wardline/ suppression state visible and auditable.
.weft/filigree/
.weft/loomweave/
.weft/warpline/

# Filigree issue tracker
.filigree/
.env
Expand All @@ -56,7 +62,6 @@ coverage.json
loomweave.yaml

# Filigree issue tracker
.weft/
.filigree.conf
.agents/skills/loomweave-workflow/.fingerprint
.agents/skills/loomweave-workflow/SKILL.md
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- **Candidate-set merge no longer scales cubically (scan DoS).** The Level-2
branch-join merges for lambda bindings (`_merge_branch_bindings`) and
receiver-type candidates (`_merge_branch_types`) deduplicated with a nested
linear scan of the growing candidate list — O(bucket²) per merge, O(N³)
across a chain of `N` one-armed branches rebinding the same name. An
attacker-authored file (~1100 such branches) could drive a default-gate scan
to ~15s and exhaust CPU on every local and CI run. Both merges now dedup via
an identity/equality set (O(bucket) per merge, O(N²) cumulative), preserving
the exact candidate set and insertion order; the demonstrated 1100-branch case
drops from seconds to milliseconds. No analysis behavior changes — the
candidate sets are identical, so no false negative is introduced.
Reviewed regression source: `eff4eed2` (wardline-c797baf28b).

## [1.0.6] - 2026-06-20

### Changed
Expand Down Expand Up @@ -1339,6 +1353,7 @@ for Python — enterprise-class trust-boundary analysis at small-team weight.
- **Packaging** — MIT-licensed; optional extras `scanner` (config + CLI) and
`weft` (HTTP integrations).

[Unreleased]: https://github.com/foundryside-dev/wardline/compare/v1.0.6...HEAD
[1.0.6]: https://github.com/foundryside-dev/wardline/compare/v1.0.5...v1.0.6
[1.0.5]: https://github.com/foundryside-dev/wardline/compare/v1.0.4...v1.0.5
[1.0.4]: https://github.com/foundryside-dev/wardline/compare/v1.0.3...v1.0.4
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ see `CLAUDE.md`).

- **TDD.** Write the failing test first.
- Keep PRs focused — one logical change per PR.
- New behavior needs tests. New `wardline.yaml` keys need a `config_schema.py` update.
- New behavior needs tests. New `[wardline]` keys in `weft.toml` need a `config_schema.py` update.
- No back-compat shims for unreleased specs — make clean changes.
- Wardline scans its own source as a CI gate; keep the tree finding-clean (or baselined).

Expand Down
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def build_record(req):

```console
$ wardline scan . --fail-on ERROR
scanned 1 file(s); 3 finding(s) — 0 suppressed (0 baseline / 0 waiver / 0 judged), 1 active -> .wardline/20260620T153012Z-findings.jsonl
scanned 1 file(s); 2 finding(s) — 0 suppressed (0 baseline / 0 waiver / 0 judged), 1 active -> .wardline/20260620T153012Z-findings.jsonl
$ echo $?
1
```
Expand All @@ -33,8 +33,8 @@ The gate trips (exit 1) and the findings land in timestamped JSON Lines under
`.wardline/` by default (`--output PATH` writes to an exact path; `--format
sarif` emits SARIF for GitHub code scanning). Wardline is agent-first — you
don't read that file by hand. Your coding agent does: ask it *"why did the scan
fail?"* and it surfaces the one active defect (the other two findings are
`NONE`-severity engine facts):
fail?"* and it surfaces the one active defect (the other finding is a
`NONE`-severity engine fact):

> **`demo.build_record`** declares return trust `ASSURED` but actually returns
> `EXTERNAL_RAW` (less trusted) — untrusted data reaches a trusted producer.
Expand Down Expand Up @@ -137,7 +137,7 @@ Prefer `weft_markers` in application code. Wardline still recognizes
| `scanner` | pyyaml, jsonschema, click | the `wardline` CLI and `wardline mcp` server |
| `loomweave` | blake3 | persisting taint facts to a Loomweave store |
| `rust` | scanner extra, tree-sitter, tree-sitter-rust | `wardline scan --lang rust` |
| `docs` | mkdocs, mkdocs-material | building the documentation site |
| `docs` | mkdocs, mkdocs-material | a local MkDocs render of `docs/` |

The LLM triage judge (`wardline judge`) is dependency-free (stdlib `urllib` →
OpenRouter) and needs no extra.
Expand All @@ -150,8 +150,9 @@ wardline install

This injects a hash-fenced instruction block into `CLAUDE.md`/`AGENTS.md`,
installs the `wardline-gate` skill, merges a `wardline` entry into `.mcp.json`,
and writes Codex's `~/.codex/config.toml` MCP entry. Agents then run the scan →
explain → fix-at-boundary → rescan loop natively. The `wardline mcp` server
writes Codex's `~/.codex/config.toml` MCP entry, detects Loomweave/Filigree
siblings, mints an attest signing key, and adds pre-commit hook config. Agents
then run the scan → explain → fix-at-boundary → rescan loop natively. The `wardline mcp` server
exposes the primary tool surface over JSON-RPC with no SDK, including scan,
filtered findings, explain-taint, fix, judge, baseline/waiver, doctor, rekey,
assure, attest, dossier, and Filigree filing tools.
Expand Down Expand Up @@ -196,23 +197,23 @@ It is **not** the right tool when you need:

## Documentation

Full documentation lives at **<https://foundryside-dev.github.io/wardline/>**.
Full documentation lives in the [`docs/`](https://github.com/foundryside-dev/wardline/tree/main/docs) tree.

| Document | Description |
|----------|-------------|
| [Getting Started](https://foundryside-dev.github.io/wardline/getting-started/) | Install, decorate, first scan |
| [Taint & Trust Model](https://foundryside-dev.github.io/wardline/concepts/model/) | The lattice, decorators, and propagation |
| [Rules](https://foundryside-dev.github.io/wardline/concepts/rules/) | The boundary, exception-flow, and sink rules |
| [Configuration](https://foundryside-dev.github.io/wardline/guides/configuration/) | `weft.toml` `[wardline]`: rules, severity, excludes |
| [Suppression](https://foundryside-dev.github.io/wardline/guides/suppression/) | Baselines and waivers |
| [LLM Triage Judge](https://foundryside-dev.github.io/wardline/guides/judge/) | Opt-in TRUE/FALSE-positive labelling |
| [Rust Support](https://foundryside-dev.github.io/wardline/guides/rust-preview/) | Preview Rust command-injection frontend |
| [Weft Integration](https://foundryside-dev.github.io/wardline/guides/weft/) | SARIF, Filigree, Loomweave, and sibling URL resolution |
| [Assurance Posture](https://foundryside-dev.github.io/wardline/guides/assurance-posture/) | Coverage posture, attestations, and trust-surface evidence |
| [Loomweave Taint Store](https://foundryside-dev.github.io/wardline/guides/loomweave-taint-store/) | Persisting taint facts |
| [CLI Reference](https://foundryside-dev.github.io/wardline/reference/cli/) | Every command and flag |
| [Trust Vocabulary](https://foundryside-dev.github.io/wardline/reference/vocabulary/) | The decorators and their arguments |
| [Agent Integration](https://foundryside-dev.github.io/wardline/guides/agents/) | Using Wardline from a coding agent |
| [Getting Started](https://github.com/foundryside-dev/wardline/blob/main/docs/getting-started.md) | Install, decorate, first scan |
| [Taint & Trust Model](https://github.com/foundryside-dev/wardline/blob/main/docs/concepts/model.md) | The lattice, decorators, and propagation |
| [Rules](https://github.com/foundryside-dev/wardline/blob/main/docs/concepts/rules.md) | The boundary, exception-flow, and sink rules |
| [Configuration](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/configuration.md) | `weft.toml` `[wardline]`: rules, severity, excludes |
| [Suppression](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/suppression.md) | Baselines and waivers |
| [LLM Triage Judge](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/judge.md) | Opt-in TRUE/FALSE-positive labelling |
| [Rust Support](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/rust-preview.md) | Preview Rust command-injection frontend |
| [Weft Integration](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/weft.md) | SARIF, Filigree, Loomweave, and sibling URL resolution |
| [Assurance Posture](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/assurance-posture.md) | Coverage posture, attestations, and trust-surface evidence |
| [Loomweave Taint Store](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/loomweave-taint-store.md) | Persisting taint facts |
| [CLI Reference](https://github.com/foundryside-dev/wardline/blob/main/docs/reference/cli.md) | Every command and flag |
| [Trust Vocabulary](https://github.com/foundryside-dev/wardline/blob/main/docs/reference/vocabulary.md) | The decorators and their arguments |
| [Agent Integration](https://github.com/foundryside-dev/wardline/blob/main/docs/guides/agents.md) | Using Wardline from a coding agent |

## Development

Expand Down
14 changes: 8 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,26 @@ a direction sketch, not a commitment — dates are deliberately omitted.

## Where we are

**0.3.0 — shipped.** The staged build (SP0–SP9) is complete:
**1.0.6 — shipped.** The staged build (SP0–SP9) is complete:

- Function-, variable-, and project-level taint over an inter-module call graph
(L1–L2 with an L3 fixed point).
- The NG-25 trust vocabulary and three opt-in decorators.
- Four policy rules (PY-WL-101..104), severity/enable config, baselines + waivers.
- 26 Python policy rules (PY-WL-101..126) plus Rust preview rules
(RS-WL-108/112), severity/enable config, baselines + waivers.
- JSONL + SARIF + native Filigree emit.
- Dependency-free MCP-over-stdio server (`wardline mcp`).
- Opt-in LLM triage judge (`wardline judge`).
- `wardline install` agent enablement.
- Opt-in Loomweave taint-store integration.
- Published to PyPI; docs site live; CI dogfoods Wardline on its own source.
- Published to PyPI; CI dogfoods Wardline on its own source.

## Scope

Wardline is deliberately **L1–L2 with an L3 project fixed point**, not an
exhaustive path-sensitive whole-program prover, and Python-only. We favor a
small, precise, opt-in rule set over broad SAST coverage.
exhaustive path-sensitive whole-program prover, and Python-first (with a Rust
preview, `wardline scan --lang rust`). We favor a small, precise, opt-in rule
set over broad SAST coverage.

## Near-term threads

Expand All @@ -39,6 +41,6 @@ Tracked in the project's Filigree issues:

## Out of scope (for now)

- Languages other than Python.
- Broad multi-language coverage beyond the Python core and Rust preview.
- A general-purpose, dozens-of-rules SAST suite.
- A hosted/cloud service — Wardline stays local-first.
1 change: 1 addition & 0 deletions docs/concepts/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ emit the canonical list:

```console
$ wardline vocab
schema: wardline.vocabulary/v1
version: wardline-generic-2
entries:
- canonical_name: external_boundary
Expand Down
3 changes: 2 additions & 1 deletion docs/concepts/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ handler to the specific exception you expect (`except ValueError:`).

### PY-WL-104 — silently swallowed exception in a trusted-tier function

Fires on a handler whose body only `pass`/`...`/`continue`/`break` — it discards
Fires on a handler whose body is only `pass`/`...`/`continue`/`break` or a bare
constant expression (a docstring-like string literal or a number) — it discards
the error with no logging, re-raise, or recovery. The failure vanishes
silently.

Expand Down
6 changes: 3 additions & 3 deletions docs/concepts/taint-algebra.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ least_trusted(INTEGRAL, ASSURED) == ASSURED # weakest link wins
absorbing top `MIXED_RAW`. After the three `least_trusted` migrations it has **no
production call site** — it is retained deliberately as the documented contrast
operator. See the ADR:
[Retain the 8-state lattice](https://github.com/foundryside-dev/wardline/blob/main/docs/decisions/2026-05-31-wardline-taint-lattice-retain.md).
[Retain the 8-state lattice](../decisions/2026-05-31-wardline-taint-lattice-retain.md).

## The discriminator: why even genuine value-merges use `least_trusted`

Expand Down Expand Up @@ -150,7 +150,7 @@ crosses between maps:

When a caller launders raw data through a `@trust_boundary` validator, `PY-WL-101`
reads the validator's **declared** output tier (`effective_return`,
`project_resolver.py:156`) — not the raw input — because the trust model treats
`project_resolver.py:268`) — not the raw input — because the trust model treats
the annotation as the contract.

This is sound for the statically-decidable property. A **broken** validator with
Expand All @@ -172,6 +172,6 @@ promises and what a value-level semantic analysis would require.

- [Taint & trust model](model.md) — the reader-facing introduction.
- [Rules](rules.md) — the checks built on this algebra.
- [ADR: Retain the 8-state lattice](https://github.com/foundryside-dev/wardline/blob/main/docs/decisions/2026-05-31-wardline-taint-lattice-retain.md).
- [ADR: Retain the 8-state lattice](../decisions/2026-05-31-wardline-taint-lattice-retain.md).
- `docs/audits/2026-05-31-taint-combination-audit.md` — the audit this spec
consolidates (findings F1–F6).
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,18 @@ pass unchanged.** Concretely:
across builds for identical source. Call-site-anchored rules that can emit more
than one finding per `(rule_id, path, line_start, qualname)` discriminate by the
sink/callee spelling plus the call node's **full lexical span**, serialized as
`{col_offset}:{end_col_offset}`. These are CPython `ast` column coordinates:
**0-based UTF-8 byte offsets**, with a `Call` anchored at its func-expression
start (so a method chain's outer and inner calls share `col_offset` but differ in
`end_col_offset`). A second engine MUST reproduce these byte-offset column
semantics — the same obligation `entity_spans` (§3) already imposes — or the
hashed join key drifts *silently* (unlike a span diff, which the parity test
surfaces). A per-line source-order ordinal was considered as a more portable
alternative but rejected: it would require the rule to sort calls by
`(lineno, col_offset)` anyway, reintroducing the column dependency without the
span's collision-completeness.
`{lineno - entity_line_start}:{col_offset}:{end_lineno - entity_line_start}:{end_col_offset}`.
The line coordinates are entity-relative so moving the whole function does not
rekey the finding; the column coordinates are CPython `ast` **0-based UTF-8 byte
offsets**. `end_col_offset` is relative to `end_lineno`, not globally unique:
multiline chained calls can share start line/column and ending column while
differing only in `end_lineno`, so both line deltas are load-bearing. A second
engine MUST reproduce these byte-offset column semantics — the same obligation
`entity_spans` (§3) already imposes — or the hashed join key drifts *silently*
(unlike a span diff, which the parity test surfaces). A per-line source-order
ordinal was considered as a more portable alternative but rejected: it would
require the rule to sort calls by `(lineno, col_offset)` anyway, reintroducing
the column dependency without the span's collision-completeness.

## Consequences

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/assurance-posture.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ are confined under the server root (the same guarantee as `scan`).

```console
$ wardline assure src/myproject --format human
Trust-surface coverage: 91.7% (11/12 boundaries reached a definite verdict)
Trust-surface coverage: 91.7% (11/12 surface item(s) reached a definite verdict)
proven: 9
defect: 1
unknown: 1 (1 engine-limited)
Expand Down
8 changes: 5 additions & 3 deletions docs/guides/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Verification is two separable checks:
true`. A mismatch may mean the tree moved on since the bundle was produced — not
necessarily tamper. The `note` field in the result says so explicitly.

The result object from both CLI and MCP `verify_attestation`:
The result object from both CLI (`attest --verify`) and MCP (`verify_attestation`):

```json
{
Expand Down Expand Up @@ -193,8 +193,10 @@ The result object from both CLI and MCP `verify_attestation`:
The `bundle` argument is required (the parsed JSON object, not a path). `reproduce`
defaults to `false`. The tool returns the result object above.

CLI exit codes for `--verify`: `0` if `signature_valid`, `1` if not. The
reproducibility result does not affect the exit code.
CLI exit codes for `--verify`: `0` if `signature_valid` (and, when `--reproduce`
is passed, also `reproduced`); `1` otherwise. So without `--reproduce` the
reproducibility result does not affect the exit code, but with `--reproduce` a
reproducibility mismatch yields exit `1` even when the signature is valid.

### CLI

Expand Down
7 changes: 4 additions & 3 deletions docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ defaults: it scans `.` with all rules enabled.
!!! warning "But unknown keys and out-of-range values in a *present* `[wardline]` table are hard errors"
Once a `[wardline]` table parses, it is validated against a JSON Schema
(draft 2020-12). The table, the `[wardline.rules]` block, the
`[wardline.judge]` block, and the `[wardline.autofix]` block all set
`additionalProperties: false`, so a typo'd key or an out-of-range value
**fails loud** — Wardline exits `2` rather than silently ignoring it.
`[wardline.judge]` block, the `[wardline.artifacts]` block, and the
`[wardline.autofix]` block all set `additionalProperties: false`, so a
typo'd key or an out-of-range value **fails loud** — Wardline exits `2`
rather than silently ignoring it.

```console
$ wardline scan .
Expand Down
12 changes: 11 additions & 1 deletion docs/guides/judge.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,22 @@ wardline judge [OPTIONS] [PATH]
Triage active DEFECTs with the opt-in LLM judge.

Options:
--config PATH
--config FILE
--model TEXT OpenRouter model slug (overrides config).
--context-lines INTEGER Excerpt radius (default 30).
--max-findings INTEGER Cap findings triaged this run.
--write Append FALSE_POSITIVE verdicts to
.weft/wardline/judged.yaml (default: dry-run).
--trust-judge-policy Allow loading judge.policy_file from the scanned
project as untrusted judge context.
--trust-judge-config Allow project judge config to select model,
context, cap, and write confidence floor.
--trust-pack TEXT Allow importing this trust-grammar pack from
weft.toml [wardline]. May be repeated.
--allow-custom-packs Allow loading custom trust-grammar packs from the
local project directory.
--strict-defaults Ignore repository-supplied custom configuration
overrides (weft.toml).
--help Show this message and exit.
```

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/legis-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ lowercase hex, where `fields` is the whole scan **minus** `artifact_signature`,
`canonical_json` is sorted-key, tight-separator (`,`/`:`), non-ASCII-preserving,
NaN-rejecting JSON — byte-identical to legis's `canonical.py`. The signer is pinned by
a golden vector captured from the real legis signer and a hermetic conformance test
([`tests/conformance/test_legis_intake_contract.py`](https://github.com/foundryside-dev/wardline)).
([`tests/conformance/test_legis_intake_contract.py`](https://github.com/foundryside-dev/wardline/blob/main/tests/conformance/test_legis_intake_contract.py)).

!!! warning "Threat model"
HMAC-SHA256 with a **shared secret** is tamper-evidence within a key-holding trust
Expand Down
Loading
Loading