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:
co-authored by
Claude Sonnet 5
parent
8471d529bc
commit
f4ded3e1a8
@@ -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");
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user