Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #105Communication

protocol-security-engineer: tamper-evident comms that resist version downgrade

A new authenticated comms plugin. It's versioned plus an HMAC-SHA256 tag over

Author

Status
Merged
Merged on
Jul 9
Branch
hackathon/ai-naymul-downgrade-safe-comms

Description

The pitch.

## What this adds

A new `authenticated` comms plugin. It's `versioned` plus an HMAC-SHA256 tag over
the whole envelope, so tampering on the wire is detectable instead of silent.

## Why

`versioned` (PR #18) fixed compatibility, but it trusts the wire. Everything —
including `schema_version` — travels in cleartext, so an on-path attacker can:

- **roll the version back** (`1.1` → `1.0`, or just delete the field) to force the
  receiver onto the older, looser contract — the classic TLS-style downgrade, and
- **strip a field** a newer peer added and the receiver never notices.

The existing comms validators check the contract but assume an honest wire, so
nothing catches this today.

## How it works

The tag covers the entire canonical envelope, keyed by a per-pair channel secret
the attacker doesn't have. Rewrite any covered byte (version included) and the tag
no longer verifies → the envelope is rejected with a `DowngradeError`. It's a
strict superset of `versioned`, so it still passes the existing versioning
validators. Untagged legacy traffic is accepted by default (`require_auth=False`)
so a rolling upgrade can proceed; flip it on once every peer is upgraded.

Pure stdlib (`hmac`/`hashlib`), no new deps, deterministic.

## Verify

```bash
uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest -v

uv run nest run scenarios/comms_downgrade_attack.yaml
python -c "from pathlib import Path; from nest_core.validators import validate_trace; \
    [print('PASS' if r.passed else 'FAIL', r.name, '-', r.detail) \
     for r in validate_trace(Path('traces/comms_downgrade_attack.jsonl'), 'comms_downgrade')]"
# PASS comms_downgrade_resistance - 12 tampered envelope(s) correctly rejected
# PASS comms_authentic_delivery  - 6 authentic envelope(s) correctly accepted
```

Switch the scenario to `comms: versioned` and the same trace fails — that's the point.

## Scope

The pairwise key stands in for an ECDH session key (this tests tamper-evidence, not
key exchange), and verbatim replay is deliberately out of scope — bind a nonce into
`metadata` to close that separately. `nest_native` and `versioned` are untouched.

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/105/head:pr-105
git checkout pr-105