mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
1bdc11f6feffd4bd56cb8539cb0bf4495629c6fc
20
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1bdc11f6fe |
exthost.ui-delivery: the refusals in the host's words, and the settings
The test held a missing package's page to any status of 400 or more, and the relay's 502 passed it: the host's refusal lost on the way to the panel looked the same as a refusal. It is 404 in the host's words now. The same relay carries a package's settings, which no test asked for through forgectrl: the test reads them, sends a value over its bound, and holds the answer to 400 in the host's words with the stored value unmoved. The edits are inside the test's own function, so no other test's fingerprint moves. Proof: on the bench reference, image 20260922152358, the image's own forgectrl fails the test (a disabled package's page 502, "the extension host did not answer"); with forgectrl 8055000 and this file bind-mounted it passes: 404 "that package is not installed", 404 "this package is disabled: its interface is not served", and 400 '"threshold" is at most 100' with the threshold still 40. |
||
|
|
5915408147 |
exthost: the effective capability list, and a disabled package's page
exthost.package-routes reads the new `effective` field beside the manifest's own list: what the package may use is never wider than what it asked for, holds everything the operator granted, and the panel's list and the host's own command line say the same thing. That is the list the panel's bridge decides on, so it is the one the case has to hold. exthost.ui-delivery gains the disabled package: its page is refused in words, and served again once the operator enables it. Disabling a package is the way out of everything it does, and its interface was the one door that stayed open. Both are changes inside the tests' own functions, so no other case's fingerprint moves. |
||
|
|
59887ac742 |
exthost.armed-freeze: the camera is shut for the window too
The case proved the freeze and stopped there. A package with the operator's job_time.run grant is not frozen, and a capture costs kernel-side work beside the step stream, so the window has a second half: no program takes a picture during a cut. From inside the same real armed window the case already opens, the sampler now takes one background capture and keeps what the machine said. It must be refused (409, in words that name the window), and a second one after the window closes must come back a JPEG. The refusal is decided before any frame is taken, so the probe costs the print nothing - and if it were served instead, the capture that happened is the one the rule forbids, which is what the case is here to catch. covers gains forgectrl's src/cam.* and src/main.c, where the refusal is. |
||
|
|
77e4c03369 |
exthost.motion-job: a package runs a program, dark and witnessed
The reference package is installed with motion.job granted, writes a program into its own data directory, and asks the host to run it. What it proves on the machine: a program named with a path and one that is not there are refused by the host, with the machine never asked; the real one is taken by the machine as the package, every line sent and acknowledged; the head moves and the head accelerometer sees it; and the laser latch stays locked with every emission witness at zero. The program commands no laser, so the runner ends the job as one that ended without a discharge. That is what a dark program is, and the test asserts it rather than glossing it: the whole program ran and nothing fired. A job that does fire is cloud.dark-print's and laser.emission-witness's. Three things the first runs taught. A button press needs an "until" - what the press is expected to do - and a dark program never opens an armed window, so there is nothing for a press to arm and the test does not press. The job's lease owner is prefixed "job:". And the job record's emission.samples counts samples taken, not samples that saw light; the witnesses are laser_on_samples, lit_s and thermopile_delta. Proven. The unit suite: 451 tests; install_test: 225 checks. On the bench reference, image 20260922014545 with the cross-built daemons mounted over the image's: exthost.motion-job PASS, with exthost.service, exthost.motion-jog and motion.job PASS beside it. Against the image's own daemons the route is 404. |
||
|
|
0ba0fb112b |
exthost.ui-delivery: a package's page, served and refused
A package that asks for ui must ship ui/index.html and one that ships it must ask: the test packs an archive each way and checks the install refuses both in words. With a proper one it checks GET /ext/ui hands the page back byte for byte, and that a package which is not installed is refused. Each control archive is packed in a directory of its own, because the packer makes a signing key there and fwup will not remake one over an existing file - the first version shared a directory and errored. Proven. The unit suite: 451 tests. On the bench reference, image 20260921225820 with the cross-built daemons mounted over the image's: exthost.ui-delivery PASS, with exthost.service and exthost.package-routes PASS beside it. Against the image's own daemons it FAILS: the older host installs a package that asks for ui and ships no file. |
||
|
|
f74382984c |
exthost.motion-jog: a package moves the machine, watched
A new catalog test for the one capability that moves the head. The reference package is installed with motion.jog and jogs on the test's word, through a file the service watches, so the motion happens at a moment the test chose and can be witnessed while it does. What it proves on the machine: the host's credential is 0600 and root's and holds motion.jog and not a camera; a jog past the bounds is refused by the host with the machine never asked; the package's jog moves the head and the head accelerometer sees it; and across the whole of it the laser latch stays locked and the emission witness stays at zero. The head is jogged back to where it started. The laser check reads the field names the machine actually publishes (laser_locked, laser.emission_samples). The first version of it asked for names that are not in /status, so it compared None to None and could not have failed. Proven. The unit suite: 451 tests. On the bench reference, image 20260921220023 with the cross-built daemons mounted over the image's: exthost.motion-jog PASS, and exthost.service and motion.port-jog PASS beside it. Against the image's own daemons it FAILS: the route is 404. |
||
|
|
75ef8bbc95 |
exthost.service: a package's camera, and how it yields
The reference package asks for the lid camera and not the head's. From inside its sandbox the test checks that the granted camera comes back as a JPEG with the image type to match, that the camera it does not hold is 403, and that a camera there is none of is 400. It then holds a stream open and asks the service for a capture through a file the service watches, so the capture happens at a moment of the test's choosing: it is refused in the machine's own words while somebody is watching, and served once the viewer stops. Proven. The unit suite: 451 tests. On the bench reference, image 20260921204711 with the cross-built forgectrl and extension host mounted over the image's: exthost.service PASS, and exthost.events, exthost.hold-pause-tier and camera.snapshot PASS beside it. Acceptance. exthost.service already covers forgeext's **; a package's camera is part of what a confined service can reach. |
||
|
|
bb28c7855a |
exthost.service: the storage quota, on the machine
The reference package declares storage:1. The test writes 2 MiB into its data directory and watches the host measure it, stop it, and show the quota's reason, then clears the data, enables it again, and sees the service come back. That is the operator's way out, so the test walks it. Proven. The unit suite: 451 tests. On the bench reference, image 20260921190848 with the cross-built extension host mounted over the image's: exthost.service PASS, and exthost.events and exthost.hold-pause-tier PASS beside it. The first run of this check caught a real bug: the reason arrived empty, because the host built it in the buffer the supervisor overwrites when it stops a service. Acceptance. exthost.service already covers forgeext's **; the quota is part of what the host does to a confined service, so it is exercised there. |
||
|
|
447f90205d |
exthost.service: the reference package has settings of its own
The package now declares three settings and, from inside its sandbox, reads them, sets two, and is refused an undeclared key and a value out of bounds. The store on disk is checked for its mode and its contents, so the test proves the host keeps them and not only that it answered. Proven. The unit suite: 451 tests. On the bench reference, image 20260921190848 with the cross-built forgectrl and extension host mounted over the image's: exthost.service PASS. Against the image's own daemons it FAILS where it must, the older parser refusing the manifest with unknown key "settings". Acceptance. exthost.service already covers forgeext's ** and forgectrl's src/main.c; the settings are part of what a confined service can reach, so they are exercised there rather than in a test of their own. |
||
|
|
952e7dc32d |
exthost.events: the machine's events reach a package
A new catalog test for the one subscription the extension host holds and the poll a package reads it with. The reference service learns from GET /v0/self whether it was granted events and polls POST /v0/events only then, so every other test keeps the half-second heartbeat those tests watch; what it polled and what it saw go in its data directory. What it proves on the machine: the host holds the stream while one package wants it and lets it go when none does; a poll that names its place and has nothing to be told comes back at its own deadline and not before, while the first poll, which only asks where the present is, comes back at once; the lid opened and closed on the fixture reaches the package as lid events, in order and numbered, which also proves a waiting poll is woken rather than left to time out; and with the three ordinary streams taken the host's subscription is still there while a LAN client that asks for the host's slot by name is refused, because only a loopback peer may claim it. The three ordinary streams each bind a source address of their own. The cap counts peer addresses, and the kernel sends every loopback connection from 127.0.0.1 whatever address it was aimed at, so three streams without that would replace each other and leave two slots free. The first run of this test said a LAN client had been admitted; it had been, correctly, to a slot the test had failed to fill. Proven. The unit suite: 451 tests, and the coverage lint passes. On the bench reference, image 20260921171224 with the cross-built forgectrl and extension host mounted over the image's: exthost.events PASS, and beside it exthost.service, exthost.armed-freeze, exthost.hold-pause-tier and events.stream PASS. Against the image's own daemons exthost.events FAILS at its first new check, as it should. The bench was left as found, GRBL mode, no mounts. Acceptance. exthost.events covers forgeext's src/evfeed.*, src/api.* and src/run.*, and forgectrl's src/events.* and src/main.c. |
||
|
|
eb0a1e7838 |
exthost.panel-install: the key goes in through the panel, and the box holds the button
The test added the owner's key by copying the file. It now adds it the way an operator does: POST /ext/key with the machine's button held. Without the button it is 409 and no file lands; a name with a space, a name that is a path, and no key at all are 400; a key that is no key is 409 from the host; with the button held it is added and listed with its id. Removed again, the same archive reads unverified, and removing a key that is not there is 409. The bench actuator's press was 200 ms, the firmware's default, and a request that must reach the machine while the button is down often missed it: the unverified install took three presses on one run and all ten on another. ctx.act() now takes `ms`, the fixture client passes it to the box (the firmware clamps it into 20 to 500), and the two button-held steps ask for 500. Both landed on the first or second press afterward. Proven. The unit suite: 451 tests, 0 undefined names (both stub fixtures take the new argument, and the fixture test pins that the box is asked for the longest press). On the bench reference, image 20260921161446 with the cross-built forgectrl and extension host mounted over the image's: exthost.panel-install PASS, exthost.package-routes PASS, exthost.hold-pause-tier PASS. Against the image's own daemons the install test FAILS, as it should. Acceptance. exthost.panel-install covers forgectrl's src/extpkg.*, src/main.c, src/auth.* and forgeext's src/main.c, src/install.*, src/pkg.*; the runner and the fixture client are harness, outside the suite and outside every fingerprint. |
||
|
|
963bded9b3 |
exthost.package-routes: the panel page carries the card, and its script is in order
The page the image serves must hold the card (extpkgs, extswitch, extfile, extstaged, extinstallphrase, extAct, loadExt) and must define loadExt before the call that runs while panel.js parses. That second check is the bug the browser found: ext.js loaded after panel.js is an undefined loadExt, and it takes the rest of the System tab's loads down with it. Proven. The unit suite: 451 tests, 0 undefined names. On the bench reference, image 20260921154037 with the cross-built forgectrl mounted over the image's: PASS. Against a forgectrl built with the scripts in the old order the order check FAILS, as it should. Acceptance. exthost.package-routes gains forgectrl's src/ui/ext.js, src/ui/index.html, and src/ui/embed.cmake in its covers map. |
||
|
|
9c36df9ee9 |
exthost.panel-install: a package installed through the panel at each tier's consent
Kind operator with the button as its action, GRBL mode, nothing moves and nothing fires; extensions stay as found and the packages never run. The reference package signed with a key nobody trusts uploads as unverified with consent button: refused without the button held (the phrase is no substitute, the staged file stays), installed while it is held, listed unverified with the hold the request granted. A person holds the button; the bench actuator's press is a half-second pulse, so the test presses again until a request has landed inside one. With the same key added as the owner's the upload reads community with consent typed, and goes through no phrase, the phrase in another case, no grant (the host's words), a grant with a shell's words, a grant that is an option of the host's, and then installs. An upload without the login, bytes that are no archive, an install with nothing staged, and a discarded upload are refused as they should be. Both packages are removed through the route and the extension root is as found, the staged file included. Proven. The unit suite: 451 tests, 0 undefined names. On the bench reference, image 20260921150233 with the cross-built forgectrl mounted over the image's: PASS (the third press landed); against the image's own forgectrl FAIL at its first request. Acceptance. The test is new. It covers forgectrl's src/extpkg.*, src/main.c, src/auth.* and forgeext's src/main.c, src/install.*, src/pkg.*, and requires exthost.package-routes. |
||
|
|
121d7d3f81 |
exthost.package-routes: the panel's package routes against the host's own state
The reference package is installed with the hold grant through the host's command line and never runs (extensions stay as found, so the test needs no takeover). GET /ext/status is refused without the login and, with it, lists the package as the host does beside enabled, safe_mode, and the host's own status with running true and the living host's pid. POST /ext/package: hold-required and hold-advisory name and un-name the package under required-holds; disable and enable change the host's state file; an action outside the closed list, an action with a shell's words in it, an id that has not the form of one, and an id with a path in it are 400 and leave the state file byte for byte; a package that is not installed is 409 in the host's words; without the login 403 with the package still there; remove takes the package, its data, and its name away. The key and the work directory are removed and the extension root is as found. Proven. The unit suite: 451 tests, 0 undefined names. On the bench reference, image 20260921140920 with the cross-built forgectrl and extension host mounted over the image's: PASS; against the image's own forgectrl, FAIL at its first request. Acceptance. The test is new: auto. It covers forgectrl's src/extpkg.* and src/main.c and forgeext's src/main.c, src/install.*, and src/state.*, and requires exthost.service and forgectrl.auth. |
||
|
|
68fd35b8b7 |
exthost: the reference package uses its API socket, and raises its own hold
The reference service talks to the host over the socket named in FFX_API and takes machine.read. exthost.service adds, from the inside: GET /v0/self names the package and what it may use; GET /v0/machine/mode is forgectrl's answer; a hold it was not granted is 403, a path the API does not have 404, a path with .. 400; FFX_API is in the fixed environment. From the outside: the socket is root's and the account's at 0660, and another pool account that connects to it gets EACCES (a new probe mode). exthost.hold-pause-tier adds the package's own word. The test leaves what to say in the service's data directory and the service passes it on as POST /v0/hold: raised, the verdict is EXT with the package's words and fire withheld; words the form does not take are refused and the hold reads as before; cleared, the verdict is OK. Then the rest as before: the host's own raise for a package that ends at every start, the operator's exits, the stale file under a suspended host. Proven. The unit suite: 451 tests, 0 undefined names. On the bench reference, image 20260921130558 with the cross-built host mounted over the image's: exthost.service PASS, exthost.hold-pause-tier PASS (the package's hold stood 1.5 s after it raised it; held 2.0 s after the service was killed; safe mode released it in 0.8 s, extensions off in 1.1 s), exthost.armed-freeze PASS with the changed reference service. Against the image's own host, which gives a service no socket, exthost.service FAILS, as it should. Acceptance. Both tests already cover forgeext whole (exthost.service) and the hold's files by name (exthost.hold-pause-tier); neither covers map changes. |
||
|
|
ee89b31c07 |
exthost.hold-pause-tier: a required hold from the grant to the verdict and out
The reference package takes the hold capability (its service ends at once when the test leaves a file in its data directory: a package that cannot speak for itself). The install is refused without the operator's grant; a granted hold is advisory until `forgeext hold <id> required` names the package under required-holds. With extensions on, the required hold stands in the host's words until the service has run healthy, then the host keeps its file fresh and clear and GET /cool/status reads OK. The test then makes the service end at every start: verdict EXT, fire_ok false, hold true, the reason naming the package, and never clear in 24 looks over the crash loop. Safe mode ends it within four seconds and leaving safe mode brings it back; marked advisory it is dropped; marked required again and the host suspended, the reason becomes that the host is not answering, and resumed it is the package's again; ext_enabled=0 ends it. The verdict is read at idle: nothing moves and nothing fires. Everything is put back as exthost.service puts it back, the required holds are empty, and the verdict is OK at the end. Proven. The unit suite: 451 tests, 0 undefined names. On the bench reference, image 20260921121235 with the cross-built forgectrl and extension host mounted over the image's: exthost.hold-pause-tier PASS (held 1.0 s after the service was killed; safe mode released it in 0.8 s, extensions off in 0.8 s); against the image's own forgectrl, whose engine knows no holds, it FAILS with the verdict OK, as it should. Acceptance. The test is new: auto, takeover. It covers forgeext's src/holdkeep.*, src/run.*, src/install.*, src/state.*, src/main.c, and forgectrl's src/holds.* and src/cool.*, and requires exthost.service. |
||
|
|
800d6890a4 |
exthost.armed-freeze: a package's service is frozen for a real armed window
The freeze is the rule that keeps a package off the core while a job can fire, and it had a host test against a stand-in forgectrl and one drill by hand. This is its test on the machine, against the real engine. The reference package runs, its heartbeat advancing twice a second. cloud.dark-print's own job and body (a 30 s square at S0, the fixture's press, the latch locked at the button, unlocked for the run, locked after) open a real armed window over it. A sampler reads five times a second: the engine's armed flag (GET /cool/status), the group's frozen state as the kernel reports it (cgroup.events), the heartbeat, the service's pid, and the latch. The test asks for a window of 10 s or more; frozen in every sample from 2 s in to the close, with the heartbeat still; the first frozen sample no later than the first unlocked-latch sample, so the freeze is in place before the run; thawed, with the heartbeat moving again, 3 s after the close; and one process throughout. exthost.service's put-back and its as-found checks became _put_back() and _as_found(), shared by both tests. exthost's top-level imports pulled the setup suite (and now the cloud suite) in ahead of it and reordered the catalog, so exthost is imported last: image, kernel, forgectrl, setup, logs, motion, cooling, laser, camera, update, cloud, exthost; 106 tests. Proven. The unit suite: 451 tests, 0 undefined names. On the bench reference, image 20260920211625 with the cross-built forgectrl and host from /tmp, the coolant taken from 27.2 to 25.4 C with an M8 session first (the cloud client starts no print above 27): exthost.armed-freeze PASS, the window open 37.5 s, the group frozen 0.42 s after it opened and 4.97 s before the latch unlocked for the run, frozen in all 172 samples from 2 s in to the close with the heartbeat still, thawed 0.62 s after the close. With the host suspended (SIGSTOP) across the window, so that nobody freezes anything, it FAILS: not frozen in 177 of 177 samples. exthost.service PASS again after the refactor (a killed host's service gone in 0.05 s, the host back and the service running after 5.6 s). The bench was left as found. Acceptance. exthost.armed-freeze is new: kind operator (the fixture presses), takeover; it covers forgeext's src/super.*, src/run.*, src/machine.*, src/cgroup.*, and forgectrl's src/cool.*, and requires exthost.service and cloud.dark-print. |
||
|
|
1e98b37e45 |
forgeext on the image, and the tests of the host and of the consent
The image installs forgeext beside forgefirm-sandbox. The recipe installs the init script from forgeext's own tree (start 91, after forgectrl at 90, whose read-only routes the host takes the machine's state from; stop 9, down before it). The host runs nothing while ext_enabled is 0, the default. setup.extensions-consent (takeover): the Extensions advisory is served and is no first-run document; ext_enabled=1 is refused without the advisory's hash, with a stale one, without the phrase, with the phrase in another case, and whole beside a write the daemon refuses, each leaving the setting and the record on disk untouched; with the hash and the phrase it is accepted, recorded under on_demand.extensions, and the data directory gains the search bit and nothing else. The setting, the directory's mode, and the record are put back, the record under a forgectrl restart. exthost.service (takeover): a reference package built on the board, signed with a key made there and added as an owner key. It is unverified before the key is the owner's and community after; the install is refused without the consent. Turned on over the advisory, the service is looked at from outside (account, no_new_privs, seccomp, group, limits, chain) and from inside (what it can read, write, dial, and open), its first line is looked for in the forgeext logger's file, safe mode stops it, a host killed with the service in its quiet loop takes it along within 2 s and comes back, and ext_enabled=0 leaves no group and no chain. The package, the key, the setting, the mode, and the record are put back, and the extension root is compared with how it was found. The reference package exists only while the test runs: no image carries it, and no image trusts its key. Proven. forgetest's unit suite: 422 tests pass, 0 undefined names. On the bench reference, image 20260920211625 with the cross-built forgectrl and host from /tmp: setup.extensions-consent PASS and, against the image's own daemon, FAIL at its first request; exthost.service PASS and, with the init wrapper's kill taken out, FAIL at the killed-host check. The first runs of exthost.service found what the host tests could not: /data/forgefirm is 0700 on the bench reference, and the service ended with EACCES on its own entry point until forgectrl opened the directory for search with the consent. Acceptance. Both tests are new. exthost.service covers forgeext whole and forgectrl's src/main.c and src/logs.*; setup.extensions-consent covers forgectrl's consent path. The recipe, the image line, and the init script's install are layer content, in the platform identity of every fingerprint. forgeext joins scripts/manifest-from-tree.py with its first pin. |
||
|
|
2894269115 |
The deny rules: the machine itself is never a destination
The way through the extension sandbox's deny rules is an allowlist, and an allowlist names addresses. The machine's own LAN address is not a fact anybody can pin: a new DHCP lease can turn a peer's address on some package's list into the machine's, and with it open the Grbl port or forgectrl's listeners to that package. ffx.nft now refuses it structurally. Everything a host sends to one of its own addresses, the LAN one included, leaves through lo, so chain pool refuses `oifname "lo"` before it looks at the allow map; the two refusals (a reset for TCP, a drop for the rest, both counted) move into chain refuse, which pool jumps to from both places. No kernel option is new: oifname is in the nf_tables core. scripts/sandbox-rules-test.py gets a destination that is not the machine: a second network namespace joined by a veth pair, with listeners of its own. A pool uid is refused on loopback, IPv6 loopback, its own LAN address, and the peer; an allow chain opens one port of the peer to one uid and nothing else; with loopback, IPv6 loopback, and the machine's own address added to that list the uid is still refused at all three while the peer still answers; a reload closes it. It needs ip and nsenter now. exthost.platform reads its counters from chain refuse, holds the rule's place ahead of the map, and adds the case on the machine: an allow chain for the last pool uid that names forgectrl on loopback and on the LAN address opens neither, and the chain is removed. Proven. The rules test passes with nft 1.0.9, and four controls each fail it: the range one uid short, the TCP reject turned to accept, the delete-table line removed, and the lo rule removed (the uid then reaches all three of the machine's addresses). On the bench reference, image 20260920211625, this rule file loaded from /tmp with nft -f and this suite file mounted: exthost.platform PASS, uid 831 refused at 127.0.0.1:443 and 172.16.1.97:443 with both on its allowlist, the counters [0, 0] to [12, 4]. Against the image's own rules the same test fails on the rule's absence, which is the control. The image's rules were reloaded after. The unit suite passes (422). Acceptance. exthost.platform gates the rule on the machine; sandbox-ci gates the file. The rule file is layer content, in the platform identity of every fingerprint. |
||
|
|
d627ee32f1 |
The extension sandbox platform: accounts, cgroups, and the deny rules
What the image holds ready before any extension package exists, so that
the first one starts inside it.
forgefirm-sandbox (new recipe, on both images):
- the account pool: ffx0 to ffx31, uid and gid 800 to 831, one group
each, /nonexistent, /bin/false, locked. Below 1000 on purpose: the
forgefirm-users render replaces only the accounts from 1000 up, so an
account reset leaves the pool alone and the read-only rootfs never
needs an account made at run time. The image's dynamic system ids
count down from 999 and stop at 997.
- an rcS script at S30: cgroup v2 mounted at /sys/fs/cgroup, the cpu,
memory, and pids controllers handed down to /sys/fs/cgroup/ffx, and
ffx marked idle-class (cpu.idle; the kernel refuses a cpu.weight on
top of it, so none is written). The firmware's processes stay in the
root group. `status` reports both halves and exits nonzero when
either is missing.
- /etc/forgefirm/ffx.nft, loaded by the same script, before the network
starts in rc5: table inet ffx, an output-hook filter with policy
accept that sends uid 800-831 to chain pool; pool looks the uid up in
the verdict map `allow`, then answers TCP with a reset (a drop would
leave a connect to time out) and drops the rest (the sender sees
EPERM), both counted. The map is the one way through: a uid mapped to
a chain of that package's destinations. Loading the file again
replaces the table, allowlists included: it fails closed.
nftables comes in as its runtime dependency, trimmed in the distro config
to the binary and its library with JSON output: no interactive shell, no
Python binding. gmp and jansson were on the image; libmnl and libnftnl are
new. The release rootfs goes from 34.5 to 34.1 MiB free.
scripts/sandbox-rules-test.py, and the workflow sandbox-ci that runs it:
the rule file loaded into a network namespace of its own and sent at from
real uids. Root, 799, and 832 are not touched; 800, 815, and 831 are
refused on 127.0.0.1 and ::1 at once, UDP with EPERM, and a receiver hears
nobody from the pool; an allow chain opens one port on one address to one
uid and nothing else; a reload closes it.
exthost.platform (new suite module exthost.py): the platform proven on a
probe process, not read off a config. In a probe group under ffx, as the
last pool uid: held to cpu.max, stopped by cgroup.freeze and running again
after, stopped at pids.max, killed by the group's own OOM at memory.max
while forgectrl keeps its pid. The 32 accounts as the boot's render left
them. Pool uids 800 and 831 refused TCP to forgectrl on loopback (both
ports, IPv4 and IPv6), to the LAN address, and to the Grbl port, at once,
UDP EPERM, with the rules' counters moving by at least the attempts, while
root reaches the same listeners. A root probe under landlock loses /etc
and TCP connects and keeps /usr; a seccomp filter returns EPERM for the
filtered call. The probe group is removed whatever happens.
Proven. The rules test passes with nft 1.0.9, the image's version, and
three controls each fail it: the range one uid short, the TCP reject
turned to accept, the delete-table line removed. The unit suite passes
(422) with no undefined name. Image 20260920211625 carries all of it (read
back from both rootfs images: 32 accounts in passwd, group, and shadow,
S30forgefirm-sandbox, the rule file and the script byte-identical, nft
with its libraries and no Python binding). On the bench reference, that
image: exthost.platform PASS (5.0 percent of the core under a 5 percent
cpu.max, 0 us frozen and 87358 us thawed over 1.5 s each, 5 of 12 forks
then EAGAIN, rc -9 with oom_kill 1 at a 24 MiB memory.max, the counters
[0, 0] to [12, 4], landlock ABI 6), and forgefirm-sandbox status reports
both halves in place.
Acceptance. exthost.platform gates the platform; sandbox-ci gates the rule
file. The recipe, the rules, and the distro option are layer content, in
the platform identity of every fingerprint.
|