Releases: stephrobert/feint
Releases · stephrobert/feint
Release list
v0.2.0
Added
feint version --check, which asks GitHub whether a newer release exists
and prints the command to install it, pinned to that version. Asked for rather
than volunteered: nothing reaches the network unless the flag is typed, and
FEINT_NO_UPDATE_CHECK=1refuses it outright. The binary never updates itself
— telling beats rewriting something somebody verified.- Brand files, in
docs/assets/brand/, withdocs/brand.mdfor what may be
done with them. The wordmark is outlines rather than text, so the lockup
renders the same on GitHub as in a slide, and the licence section states what
the Apache licence does not cover: the name and the logo.
v0.1.0
The first published version. It carries three emulated providers on one port,
the lifecycle verbs, and the machinery that keeps the emulated surface measured
against the providers' own SDKs rather than followed by hand.
Added
- Three emulated providers on one port. Scaleway (Instance, VPC, IPAM, IAM,
marketplace), Outscale (Vms, keypairs, catalogue, Nets and Subnets) and
Exoscale (instances, catalogue, SSH keys), served by one binary with no
external Go dependency. - Lifecycle commands:
start,stop,restart,wait,status,logs.
The binary backgrounds itself — no Docker, no&— which none of the
comparable emulators can do. feint env <provider>, soeval "$(feint env scaleway)"is enough to
point a real client at the emulator. Exports on stdout, caveats on stderr.feint probe: every mounted route driven from its provider's own API
description, proving the protocol without a client installed. It never counts
towards the conformance score.feint docs: the README's coverage tables, its startup banner and the
contract policy table indocs/limits.mdare generated from the committed
artefacts, and--checkfails when they drift.- Response validation against the providers' own OpenAPI documents, with the
strength of each contract recorded rather than assumed. - Real machines behind the API through Incus, with an addressing plane that
is arithmetic: masks bounded, containment and overlap enforced, address counts
computed, and a VM placed on a subnet carrying the address the API published. - Conformance suites driving the real clients:
scw,oapi-cli,exo,
Terraform and OpenTofu. feint doctor: the host diagnostics that encode the traps this project
already paid for — the port, the Incus version ACLs need, what--vm auto
would actually pick here, the clients on PATH, and theProxyJumpin
~/.ssh/configthat makes a workingsshdlook absent.feint snapshot save/load/list/rm, over two new admin routes
(GETandPUT /_feint/state). A snapshot is exactly whatserve --state
writes, taken mid-run rather than at exit, so a fixture can be reached once and
returned to as often as a test needs. Loading replaces the store: a fixture
must not depend on what the session did before it.- A generated route reference (
docs/routes.md) and a generated client
version matrix in the README, both underfeint docs --check. The first is
written by the binary that mounts the routes; the second is read from the
workflow that installs the clients.
Security
servebinds127.0.0.1by default, where it previously bound every
interface. This emulator accepts every credential without checking one and,
with--vm, starts containers with the operator's privileges; the old default
offered that to whatever network the machine was on.--addrstill exposes it
for anyone who decides to.- The conformance suites refuse to run a client that is not pointed at a local
emulator (tools/conformance/guard.sh). Every official client falls back to
stored credentials when the environment says nothing, so an empty environment
has to fail loudly rather than reach a paying account. PUT /_feint/statebounds the body it reads (64 MiB). It decoded whatever
arrived, which made an oversized request a crash — the class of defect
SECURITY.mddeclares in scope.- DNS rebinding is refused. Binding to the loopback stops a network, not a
browser: a page the operator visits can resolve its own name to127.0.0.1
and drive the emulator from there — reading and replacing its state, and with
--vm, starting containers. An audit reached/_feint/healthwith
Host: evil.exampleand got a 200. When the listen address is a loopback one,
requests whoseHostnames anything else are now refused;--addron any
other address turns the check off, because that exposure was asked for.
Fixed
- The OVN mode could not create a single network on a fresh host. The driver
built its uplink without delegating any route, so Incus refused every network
whose block fell outside the uplink's own /24 — which is every block, since a
client picks its address plan from what it is testing. It now delegates each
block as the network is created, one at a time: delegating a whole range up
front turns into real host routes and collides with whatever already lives
there. Invisible on a machine whose uplink predated the check; found by
installing on a clean one and asking for one subnet. - A delete racing a boot could resurrect the machine. Write-backs after a
runtime call go throughstore.Commit, which refuses to re-insert a resource
deleted meanwhile. - A machine that failed to start reported
running. It now reaches the
provider's own failed state, while the documented no-runtime mode still reaches
running. - A page number nobody sends panicked every list route, taking the response
with it. - Outscale's
SubnetIdwas accepted and dropped, so a Vm reported success
and went nowhere. stop_in_placeansweredstopped, a state no client asked for. The SDK
declaresstopped in placebesidestopped, and the Terraform provider polls
for the exact one itsstate = "standby"requested: the plan failed with
"expected state stopped in place but found stopped". Neitherscwnor the
conformance suite exercises standby, which is why a real provider found it
first.per_pagewas ignored on everyinstance/v1list route. That product
spells the page sizeper_page; the newer ones (vpc,ipam) spell it
page_size, and the shared helper read only the second. Every instance list
served fifty items whatever the client asked for. Both spellings are read now.ListServersignoredstate,tagsandcommercial_type. A filter that
is dropped is worse than one that is refused:state=runningreturned every
stopped server there was, in an answer shaped exactly like the right one.UpdateServerdroppedcommercial_typeandvolumes. Retyping a server
answered 200, changed nothing, and left Terraform asking for the same change on
every plan. Both are applied now, with the restriction the SDK documents —
a type cannot change while the server runs — and a volume named by an id that
does not exist is refused rather than silently ignored.- Fields a client sends that no handler reads now fail the conformance run.
The emulator had been recording them at/_feint/conformanceall along, and
nothing looked: it listedcommercial_typeandvolumesunder
unread_request_fieldsrun after run while Terraform looped. An introspection
nobody gates on is a confession nobody hears.