MINT: add a FORTH-79/83-standard-words-only lockdown personality

Captain Bob, 2026-09-07: "starting with that 00 user we created, we're
going to give access only to FORTH 79 and 83 standard words. everything
else is locked down."

New capsules/acl-std79.4th (blocks 4023-4047): walks a VM's own
dictionary (>LINK/LINK> traversal, same as ACL-INIT-PRIMITIVES/WORDS
already use) and permanently denies+pins every word not on an explicit
FORTH-79/83 allowlist, extracted from the real registered word set
(stack_words.c through control_words.c), not recited from memory.
Deliberately excludes, beyond plain non-standard words: BYE (100% ACL
bypass to the emergency console -- "needs more discussion, exclude for
now"), COLD/WARM/REBOOT/SAVE-SYSTEM (system lifecycle), the block/screen
editor L/S/SHOW/EDIT/UPDATE/SAVE-BUFFERS (lets a session rewrite
persistent block/capsule content, defeating the lockdown even though
nominally standard), BLK-ACL-*/BLK-OWNER@ (StarForth-specific), and
FORGET/FENCE (flagged as an unrestricted superpower word, 2026-09-03
audit). Keeps WORDS/VLIST/SEE (introspection only -- ACL is enforced
per-target-word at execution time regardless of how an XT was
obtained) and the parenthesized control-flow runtime primitives
((BRANCH) etc. -- IF/DO/LOOP compile calls to these; denying them
breaks ordinary control flow, not security).

MintPersonality enum (capsule_mint.h) lets capsule_mint_identity()
select which personality-source template gets written to a new
identity's devblock -- MINT_PERSONALITY_DEFAULT (unchanged) or
MINT_PERSONALITY_STD79_LOCKDOWN (EXECs acl-std79.4th then
ACL-LOCKDOWN-STD79 as the VM's own last bootstrap step). The actual
restriction logic stays entirely in FORTH per .claude/CLAUDE.md's
Word-Level ACL System rules ("ACL policy belongs in ACL.4th, never in
C") -- capsule_mint.c only picks which few-line bootstrap stub to
write. MINT's own stack signature gains a trailing restrict? flag;
capsule_zuse_boot.c's genesis mint (Zuse herself) explicitly passes
MINT_PERSONALITY_DEFAULT -- the superuser is never restricted.

Two real bugs found and fixed live during testing, both the same class
of self-referential fault: ACL-LOCKDOWN-STD79's own walk loop calls
ACL-STD79-ALLOWED?/ACL-STD79-LIST/ACL-ALLOW!/ACL-PIN on every single
iteration to do its job -- none of those are FORTH-79/83 standard
words, so the walk was denying its own load-bearing infrastructure
partway through and then faulting the next time it tried to call it
("VM fault -- emergency console disabled; halting", reproduced twice
live). Fixed by explicitly protecting all four in the allowlist
(block 4047) -- they must stay allowed for the walk to finish, not
because they belong on a "standard words" list.

Verified live end-to-end: minted a throwaway test identity with the
restrict? flag, confirmed her WIREBIND birth completes cleanly (no
faults, no shadow conflicts) on a single real attach, then USE'd into
her VM and confirmed standard arithmetic and user-defined words work
(1 2 + . -> 3; : X 5 5 * . ; X -> 25) while KILL is entirely unknown to
her dictionary and VM-EXEC is denied. One real, non-fatal side effect
found and left as-is (not asked to fix): the fleet's inter-VM messaging
pump (MSG-ARENA) is also denied by the lockdown, logging a harmless
per-idle-tick warning -- a fully locked-down VM doesn't participate in
message routing.

Not yet applied to the real identity 00 -- this commit is the
mechanism, verified against a disposable test identity only.

Three-arch clean qemu acceptance (single Zuse device, standard
regression case) passed on amd64, aarch64, and riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
Robert Allan James
2026-09-07 15:41:56 -04:00
co-authored by Claude Sonnet 5
parent 8471d529bc
commit f4ded3e1a8
10 changed files with 27224 additions and 9 deletions
+34 -3
View File
@@ -52,6 +52,28 @@ static const char MINT_DEFAULT_PERSONALITY[] =
": WELCOME ( -- ) .\" Minted identity -- default personality\" CR ;\n"
"WELCOME\n";
/* Restricted personality: FORTH-79/83 standard words only, everything else
* denied. The actual allowlist and the walk-and-deny logic live entirely in
* capsules/acl-std79.4th (ACL policy belongs in FORTH, never in C, per
* .claude/CLAUDE.md's Word-Level ACL System rules) -- this stub's only job
* is to EXEC that capsule and then call the one word it defines,
* ACL-LOCKDOWN-STD79, as this VM's own last bootstrap step. Block 4998:
* unused in the static capsule block-map (confirmed against
* capsules/BLOCK_MAP.md, 2026-09-07) -- 4999 is MINT_DEFAULT_PERSONALITY's
* own block above, same convention. EXEC itself is deliberately not on the
* std79 allowlist, so it's still usable here (nothing has been locked down
* yet at this point in the script) but becomes permanently denied the
* moment ACL-LOCKDOWN-STD79 finishes -- this is the one and only legitimate
* use of EXEC this VM will ever get. */
static const char MINT_RESTRICTED_PERSONALITY[] =
"Block 4998\n"
"S\" common:messaging.4th\" EXEC\n"
"MSG-CD-INIT\n"
"S\" acl-std79.4th\" EXEC\n"
"ACL-LOCKDOWN-STD79\n"
": WELCOME ( -- ) .\" Minted identity -- FORTH-79/83 standard words only\" CR ;\n"
"WELCOME\n";
/* Write exactly one devblock (4096 bytes) at devblock offset `devblock`,
* as 4 consecutive 1KiB forth-block writes -- same convention
* homeblocks_sig_check()'s own read loop already uses. */
@@ -145,6 +167,7 @@ MintResult capsule_mint_identity(struct blkio_dev *dev, VM *issuer_vm,
const char *full_name, const char *username,
const char *email, const char *phone,
uint8_t out_pubkey[32], uint8_t out_seed[32],
MintPersonality personality,
int drive_known_blank) {
if (!dev || !full_name || !username) return MINT_ERR_WRITE_FAIL;
@@ -231,11 +254,19 @@ MintResult capsule_mint_identity(struct blkio_dev *dev, VM *issuer_vm,
if (write_devblock(dev, MINT_IDENTITY_SRC_OFFSET, (const uint8_t *)&idrec) != 0)
return MINT_ERR_WRITE_FAIL;
/* Default personality source, in the devblock right after the seed
* record -- RUNCAP reads exactly this layout. */
/* Personality source, in the devblock right after the seed record --
* RUNCAP reads exactly this layout. Template selected by `personality`;
* see MintPersonality's own doc comment (capsule_mint.h) for why the
* actual restriction logic isn't here. */
const char *personality_src = (personality == MINT_PERSONALITY_STD79_LOCKDOWN)
? MINT_RESTRICTED_PERSONALITY
: MINT_DEFAULT_PERSONALITY;
size_t personality_len = (personality == MINT_PERSONALITY_STD79_LOCKDOWN)
? sizeof(MINT_RESTRICTED_PERSONALITY) - 1
: sizeof(MINT_DEFAULT_PERSONALITY) - 1;
uint8_t personality_block[4096];
memset(personality_block, 0, sizeof(personality_block));
memcpy(personality_block, MINT_DEFAULT_PERSONALITY, sizeof(MINT_DEFAULT_PERSONALITY) - 1);
memcpy(personality_block, personality_src, personality_len);
if (write_devblock(dev, MINT_IDENTITY_SRC_OFFSET + 1u, personality_block) != 0)
return MINT_ERR_WRITE_FAIL;
@@ -118,6 +118,7 @@ void capsule_zuse_boot_try_attach(struct blkio_dev *dev,
MintResult r = capsule_mint_identity(dev, (VM *)0, "Zuse", "zuse",
(const char *)0, (const char *)0,
pubkey, seed,
MINT_PERSONALITY_DEFAULT, /* Zuse is the superuser -- never restricted */
1 /* sig_rc already confirmed BLANK above */);
if (r != MINT_OK) {
console_println("Zuse: genesis mint failed");
+14 -3
View File
@@ -881,11 +881,16 @@ static int mint_pop_string(VM *vm, char *dst, size_t dst_cap)
}
/**
* @brief MINT ( fname-c fname-u uname-c uname-u email-c email-u phone-c phone-u -- ok? )
* @brief MINT ( fname-c fname-u uname-c uname-u email-c email-u phone-c phone-u restrict? -- ok? )
* Mint a fresh identity onto the currently attached USB drive, with a
* real human profile (FABRIC-2.md §F.20). full_name/username required
* and non-empty; pass a zero-length string (S" ") for email/phone to
* leave them null.
* leave them null. restrict? nonzero mints with the FORTH-79/83-only
* lockdown personality (MINT_PERSONALITY_STD79_LOCKDOWN,
* capsules/acl-std79.4th applies the actual restriction at that VM's own
* first birth) instead of the default, unrestricted one -- Captain Bob,
* 2026-09-07: "give access only to FORTH 79 and 83 standard words,
* everything else is locked down."
*/
static void mama_word_mint(VM *vm)
{
@@ -894,7 +899,9 @@ static void mama_word_mint(VM *vm)
char username[USER_IDENTITY_USERNAME_MAX];
char full_name[USER_IDENTITY_FULL_NAME_MAX];
/* Stack order: fname pushed first, phone last -- pop in reverse. */
/* Stack order: fname pushed first, restrict? last -- pop in reverse. */
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t restrict_flag = vm_pop(vm);
if (mint_pop_string(vm, phone, sizeof(phone)) != 0) return;
if (mint_pop_string(vm, email, sizeof(email)) != 0) return;
if (mint_pop_string(vm, username, sizeof(username)) != 0) return;
@@ -907,8 +914,12 @@ static void mama_word_mint(VM *vm)
return;
}
MintPersonality personality = restrict_flag
? MINT_PERSONALITY_STD79_LOCKDOWN
: MINT_PERSONALITY_DEFAULT;
MintResult r = capsule_mint_identity(dev, vm, full_name, username, email, phone,
(uint8_t *)0, (uint8_t *)0,
personality,
0 /* not pre-checked -- keep the safety check */);
switch (r) {
case MINT_OK: