| 2026-07-15 |

C2 sub-task (i): sandbox re-verified faithful; fidelity-checker scope false-positive fixed
...
Re-ran the HARDENED fidelity check against office1-netbox (the VR1 IPAM apex,
DOCFIX-195) -- read-only dump via the consolidated sandbox token + an SSH tunnel.
Result: exit 0, "upstream draft + EXACTLY the planned delta (D-115/D-117/D-118)."
C2 sub-task (i) is CLOSED; the sandbox is proven faithful.
The hardened checker false-failed on 10.10.0.0/16 and 172.30.0.0/16 being
unscoped -- but those two role-container /16s are unscoped BY DESIGN
(d115-office-carve.py), matching the 27 unscoped role containers already in the
upstream apex. A full cross-check of all 44 delta prefixes against their intended
scope was 44/44 (every DC prefix on the correct DC site; no wrong-target binding).
Fix (netbox/sandbox-fidelity-check.py): assert scope == INTENDED per delta prefix
(EXPECTED_PREFIX_SCOPE; DC half generated by _dc_prefix_scopes so the CIDR set and
scope map cannot drift). Strictly stronger than the old "has a scope?" test -- still
catches a dropped scope, and now catches a WRONG-TARGET binding (a DC0 prefix bound
to vr1-dc1, the D-117 near-miss). NOT fixed by exempting the two CIDRs.
Harness moved to dump format (scope_site), T1 sets each prefix's scope from the
checker's own map, +T9 (wrong-target fails) +T10 (legit-unscoped passes). 10->12.
Gauntlet ALL GREEN (58); repo-lint 0 fail.
SEC-009: the sandbox NetBox token had been MISSED by the consolidation (lived only
at /root/netbox-secrets/api.token on office1-netbox); now consolidated to
~/vr1-office1-creds/vr1-netbox-sandbox.env (0600), copied off the VM without
printing. Ledger addendum recorded.
No NetBox state changed -- dump and check are read-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|

Consolidate credential/env sprawl into ~/vr1-office1-creds/ (SEC-009); seed memory
...
Operator-approved. Three env files were loose in ~ outside the consolidated creds
folder -- .vr1-netbox.env, vr1-office1.env, and vr1-stage1.env. The last was mode
0664 (world-readable) and held TF_VAR_maas_api_key: a real MAAS-secret exposure,
not just untidiness. None created this session (mtimes 07-10..12).
Done (files outside the repo; recorded in the changelog):
- moved all three into ~/vr1-office1-creds/; chmod 600 on every sensitive file
(fixed the two 0664 files: vr1-stage1.env and tailscale-authkey.txt).
- no CONTENTS read -- key NAMES only, to identify each file's purpose.
- fixed a D-119 follow-up the in-repo sweep could not reach: vr1-stage1.env had
TF_VAR_dc1_pool_path/dc2 -> renamed to TF_VAR_vr1_dc0/vr1_dc1_pool_path.
Repo (committed):
- active path refs old ~/.vr1-netbox.env -> ~/vr1-office1-creds/vr1-netbox.env in
prod-draft-dump.py, the phase1 runbook Step 10b, as-built, and the ledger. Dated
changelog/incident records left as historical snapshots.
- as-built secrets table: the three env files + the convention. CORRECTED a
mid-session wrong assumption -- the SANDBOX token is NOT on vcloud; it is on
office1-netbox (/root/netbox-secrets/). vr1-netbox.env is the UPSTREAM token only.
- security-ledger SEC-009 (REMEDIATED) + the standing per-site consolidation convention.
- seeded agent memory (was EMPTY): the creds convention + the closed-test posture,
so neither is re-lost at compaction.
STANDING CONVENTION: per-site ~/<site>-creds/ (0700; files 0600); no loose env in ~;
~/vr1-dc0-creds/ and ~/vr1-dc1-creds/ follow the pattern from day one. Data-loss /
continuity control first; real cred hardening deferred to post-teardown (closed test).
GAUNTLET ALL GREEN (58). repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| 2026-07-13 |

Tailscale: office1-tailscale joined the SELF-HOSTED tailnet as the Office1 subnet router
...
Operator's control plane is self-hosted (https://tailscale.baldurkeep.com:443), NOT Tailscale's
public one -- so the public login-URL flow from the previous commit was void. Node reset off
controlplane.tailscale.com and re-enrolled against the operator's server with their auth key.
REACHABILITY PROVEN FIRST, not assumed: DNS + TCP 443 + HTTPS to tailscale.baldurkeep.com confirmed
from the node BEFORE running `tailscale up`, so a failure would have been a network fact rather than
a confusing registration error.
AS BROUGHT UP: --login-server=https://tailscale.baldurkeep.com:443 --authkey=<from 0600 file>
--advertise-routes=10.10.0.0/22 --accept-routes --accept-dns=false --hostname=office1-tailscale
Online as 100.64.0.53 (fd7a:115c:a1e0::35), no health warnings, advertising 10.10.0.0/22 -- exactly
the D-115 Office1 carve, so D-107's "Office1 ONLY" front door is enforced by the CARVE itself rather
than by policy alone.
ONE DELIBERATE DEVIATION from the operator's usual command: --accept-dns=false. This is a
MAAS-managed machine and MAAS's bind9 serves its internal names; letting the tailnet take over DNS
would break that resolution. Flagged rather than silently applied -- trivial to flip if MagicDNS is
wanted here.
OPERATOR ACTION STILL REQUIRED: the 10.10.0.0/22 route is ADVERTISED but must be ENABLED on the
control server (Headscale: `headscale routes enable`) before other tailnet nodes route to Office1.
Once enabled, the whole Office1 carve -- edge GUI, MAAS UI, NetBox -- is reachable from the tailnet
with NO SSH tunnels, superseding most of the tunnel commands in the as-built doc.
SECRET HANDLING: the auth key was written by the operator to a 0600 file OUTSIDE this session,
consumed BY PATH, never printed into context, and the copy shipped to the node was SHREDDED after
enrolment (tailscaled holds its own node key now). Tracked as SEC-008 -- a rotation obligation, not
a delete-me, since the key remains on vcloud for re-enrolment.
docs/vr1-office1-as-built.md updated with the tailnet addresses, the exact bring-up command, the
DNS deviation, and the pending route-enable step.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|

D-113(a2) PROVEN: the OPNsense REST API round-trips the edge config
...
The API path adopted in D-113 is now demonstrated working against the LIVE Office1 edge,
closing the question that gated the ruling.
Measured:
GET core/firmware/status -> HTTP 200, OPNsense 26.1 (auth works)
GET kea/dhcpv4/get -> dhcpv4.general.enabled=1, interfaces=lan
POST kea/dhcpv4/search_subnet -> 1 row: subnet 10.10.0.0/24, pools 10.10.0.100-10.10.0.199,
option_data.routers 10.10.0.1, dns 10.10.0.1,
domain office1.vr1.cloud.neumatrix.local, descr "LAN DHCP"
The API reads back EXACTLY what we pushed via config.xml yesterday, and exposes it as TYPED,
NAMED FIELDS -- not free-form XML. That is the (a2) argument made concrete: DOCFIX-191 (missing
sshd), DOCFIX-192 (silenced console) and DOCFIX-193 (an inert ISC <dhcpd> block against a Kea
backend) are NOT EXPRESSIBLE here. You cannot omit a typed field that has a default, and you
cannot address the wrong DHCP backend when the endpoint IS the backend.
GUI gotcha worth recording (26.1): API keys are NOT created in the user-edit dialog, and NOT in
the ApiKeys tab -- that tab wires only search_api_key + del_api_key, so it can list and delete
but CANNOT create. Creation is a ROW ACTION on the Users list: the fa-ticket icon, "Create and
download API key for this user" (source: mvc/app/views/OPNsense/Auth/user.volt). Any instruction
saying "edit user -> API keys -> +" describes the OLD UI and is wrong for 26.1. ApiKeyField::add()
mints key+secret as base64(random_bytes(60)) and stores key|crypt(secret,'$6$') -- the secret is
shown EXACTLY ONCE.
API gotchas: response root key is `dhcpv4`, NOT `dhcp4` (the config XML element name differs from
the API key name -- do not infer one from the other). camelCase actions map to snake_case URLs:
searchSubnetAction -> POST /api/kea/dhcpv4/search_subnet.
SEC-007 amended: ~/vr1-office1-creds/ now also holds the OPNsense API key/secret (0600).
NOT DONE: no WRITE through the API yet -- that is a live mutation and stays gated. And the
config.xml template is still in place: DO NOT push a full rendered config.xml now, it would
clobber API-managed state. Next step is reducing the template to a MINIMAL bootstrap (sshd +
root key + console) and moving DHCP/firewall/interfaces to the API.
Revert: read-only against the edge + docs; nothing to undo. To revoke the credential: GUI ->
System > Access > Users > root row > ApiKeys tab -> delete, then rm ~/vr1-office1-creds/opnsense-api.txt
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6Fzre1CxCY8tzwFCudu57
|

SEC-006/SEC-007: put the two known credential exposures on the ledger
...
Both were flagged only in changelog/session-ledger prose, never as ledger rows -- exactly
the failure mode the security-ledger header warns about ("never only a comment in a script
header, where the libvirt item lived for a week"). An exposure with no row has no owner, no
status, and no revoke trigger.
SEC-006: the NetBox API token pasted into agent chat context during the D-111 IPv6 work.
This one is BURNED, not merely due for rotation -- it exists in a transcript outside our
control, so it needs revoke + reissue. Operator ruling 2026-07-13: DEFERRED, revoke at
completion of this deployment. Recorded as OPEN and explicitly live-and-exposed until then,
so the deferral is visible rather than forgotten.
SEC-007: ~/vr1-office1-creds/ holds the Office1 edge root password, its bcrypt hash, and the
office1_svc SSH private key in plaintext (0700/0600). This is a ROTATION obligation, not a
delete-me: D-112(c) makes SSH the only management path to the edge, so the key must exist.
No change to any live system; documentation only.
Revert: git revert this commit (the rows are the only change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6Fzre1CxCY8tzwFCudu57
|

SEC-005: store a GitBucket credential on the jumphost so pushes stop failing
...
The jumphost clone could not push (could not read Username for https://git.baldurkeep.com),
which left this branch 12 commits ahead of origin for two days -- all the D-112 / OPNsense
edge work existed only on this box. Now fixed: credential.helper store + a dedicated PAT at
~/.git-credentials (mode 600), written by the operator via a hidden prompt. The token was
never read into agent context; the credential is verified by USING it, never by printing it.
Two GitBucket behaviours measured and recorded, because both cost a cycle:
1. The git username is NOT the web-UI login. The operator signs in with an email; that is
rejected for git transport. Measured against info/refs?service=git-receive-pack:
jesse.austin@neumatrix.com -> 401, JANeumatrix (the git user.name) -> 401,
jesse.austin -> 200 CAN PUSH. Note GitBucket 401s PAT basic-auth on /api/v3/ across the
board while accepting it for git -- so validating a credential against the API gives a
FALSE NEGATIVE. Validate against the receive-pack endpoint.
2. credential.helper store DELETES the stored credential when the server rejects it (git
calls credential reject on a 401). The first bad-username push therefore emptied
~/.git-credentials to 0 bytes, and the follow-up diagnostics were authenticating with an
empty string -- which read as a corrupt file and misdirected the investigation. If the
file mysteriously empties: the credential was rejected, it did not fail to write.
Tooling consequence: validate BEFORE writing.
SEC-005 is OPEN by operator ruling: a long-lived, account-wide PAT now sits in plaintext on
the jumphost (GitBucket has no per-repo token scoping, so the control is rotation, not
scope). Revoke/rotate at v1 close, or immediately if the jumphost is shared or rebuilt.
Revert: git config --global --unset credential.helper && rm -f ~/.git-credentials,
then revoke the token in GitBucket -> Account Settings -> Applications.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6Fzre1CxCY8tzwFCudu57
|
| 2026-07-06 |
addendum 20: operator ruling sweep -- D-050 closed via D-051, D-073 adopted (list_trusts hardening, impl pending), SEC re-triggers, D-072 upstream bug withdrawn
...
Also fixes a self-inflicted scan poisoning: the 2026-07-05 ledger finding note
quoted high-numbered decoy identifiers literally, inflating ledger-scan's
DOCFIX/BUNDLEFIX next-free; tokens de-fanged + standing rule recorded. D-050
block Status line made scan-closable (D-063 precedent, keywords avoided).
Scan verified post-change: open = D-068, D-071; next-free D-074/DOCFIX-091/
BUNDLEFIX-012; fences 4/0/0; files ASCII.
Revert: git revert this commit (doc-only; no cloud state touched).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
| 2026-07-03 |
|