The committed settings enabled the caveman and ponytail plugins from two
unpinned GitHub marketplaces for everyone who trusts the repository, and
plugin hooks run in the harness, outside the Bash sandbox. The block is
a leftover: since kevinveenbirkenbach/skills@3be5655b44, make
install-skills writes it to .claude/settings.local.json, so enabling the
plugins stays a per-developer choice.
Found while reviewing the settings after Hanno Horrmeyer's report; see
7ed60e1fc2.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7ed60e1fc2 renamed the Assumption 10 heading in security.md, and the
link in sandbox.md still used the short #assumption-10 fragment, which
no heading slug matches. It now carries the full slug of the new
heading, the form 4efa892706 on feature/store-i18n uses for the old
one, so that branch's merge resolves the line by keeping main's side.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mailu retired its 1.7 documentation, so the link answered 404 and
failed the external URL check. 2024.06 is the version meta/services.yml
deploys, so the reader lands on the documentation for the release they
are running; master is Mailu's unreleased trunk.
The line is identical to c333fdef20 on feature/store-i18n, which keeps
that branch's merge into main free of a conflict on it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An external report flagged the Bash(*) allow in the committed
.claude/settings.json. Bash(*) stays: every command still runs inside
the OS sandbox, and deny and ask take precedence over it. Reviewing the
file against its own documentation turned up two real gaps instead.
security.md and settings.md cite an rm -rf* deny rule as the guard
against `rm -rf .` wiping the worktree and .git/, but the rule was lost
in ee30f6d446 when the deny list was rewritten. It is back.
denyRead covered ~/.aws, ~/.config/gcloud, ~/.gnupg and ~/.kube only.
~/.docker/config.json holds registry logins inline, ~/.git-credentials
and ~/.netrc hold plaintext credentials, and the default ~/.ssh/id_*
keys were readable because Assumption 10 kept ~/.ssh open for an
ask-gated git push that has since moved to deny. All nine paths are
denied now. The rest of ~/.ssh stays readable, so SSH fetches keep
known_hosts and authenticate through the agent socket. Sandboxed docker
calls pull anonymously and print a config warning on stderr.
Thanks to Hanno Horrmeyer for the report.
Reported-by: Hanno Horrmeyer
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Asserts the hub's two mesh entries carry the same key and that its spoke holds
it. A first attempt matched only the first entry and passed against the bug.
A host in two meshes has one private key, so the second mesh must reuse what
the first minted. The lookup prefers the entry of the mesh being written, which
after a rotation is the stale pre-rotation key, so the hub ended up advertising
one key on the swarm plane and another on the data plane while holding the
private half of only one. Its workers could never handshake again.
The run now carries what it decided: a host keyed while planning one mesh keeps
that key for every mesh after it.
mailu.io/1.7/compose/setup.html is gone; the versioned tree was restructured
and 1.7 predates it. The external suite fails the pipeline on it, which skips
every deploy chunk.
The second write rotates, so the converge, the controller join and the second
pass all run against keys that changed mid-deploy. Also trims the reset
narration.
write_mesh takes rotate, so the round mints a fresh keypair for every member
between the passes and the converge that follows carries them. The rotation is
asked for rather than inherited from a mirror that happened to wipe the keys,
which is what the requirement's two rotation criteria describe.
It was there to name the cause when the guard fired on a healthy host. The
cause is settled -- the whole-stream comparison, not the mount -- so the probe
goes back to reporting state alone.
Both readers move together: the assert and the when that unmounts a foreign
source. The controller runs locally today, so this keeps the two call sites
reading one contract rather than fixing only the one that broke.
The mount was present on every node while this assert reported local data:
the second pass reaches them over ssh with a password, which allocates a pty,
and the whole-stream comparison cannot survive that. The failure now carries
what the probe saw.
Ansible allocates a pty whenever become needs a password, and a pty merges
stderr into stdout, so a caller matching the whole stream reads login noise as
the answer. The state is tagged so a match is a substring rather than the
whole stream.
The local-data branch also lists what it found, because 'migrate or remove the
local tree manually' is not actionable without it. Temporary: it comes out
once the transport question is settled.
The existing-key lookup walked the inventory hosts, and the controller joins
by name rather than through a group, so it was the one member whose key was
never found and always minted fresh. Every write then changed the hub's peer
list, and the converge re-keyed the swarm tunnel for no reason.
all_hosts already accounts for the controller two lines above; the lookup now
uses it, as the prune already does.
9850d5afa0 disabled the job-level deploy group because it blocked the
queue and kept jobs from being cancelled. Both causes are addressed:
- Cancelling: the chunks that call this workflow guarded with always(),
so a cancelled run kept its deploy jobs alive and they kept holding
their groups. Since a5f00ca777 the chunks guard with !cancelled() and
die with the run, which releases the group.
- Blocking: the key held only mode, tor, apps and variant, so rows that
differ in distro or filesystem waited on each other across branches.
The key now carries every axis the matrix generator emits, the same
set artifact_slug identifies a deployment by: mode, tor, distro,
filesystem, apps and variant. Two runs now only meet on an exactly
identical deployment. A sample matrix of eighteen rows had every axis
set and no duplicate key within the run.
queue: max keeps the second identical row queued instead of replacing
the waiting one; GitHub accepted the key in all 200 runs of August,
and scripts/lint/action.sh already ignores actionlint's unknown-key
report for it.
No resource shared per row was found in the deploy path: jobs push no
images, write nothing through the API and use no actions/cache, and
hosted runners are isolated. The serialization is the cross-branch
ordering ae9d7c2fd3 introduced, not the protection of a resource.
A TODO asks a CI deploy to confirm that a cancel drops queued jobs
from the group and that an identical row of a second run queues
behind it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On pull_request and pull_request_target the deploy and workspace
matrices now run fail-fast: the first failed row cancels its siblings
in that matrix. A deploy chunk holds around sixty rows, and hosted
runner slots have been the bottleneck, with PR jobs waiting from twenty
minutes to twenty-one hours for a runner. Once one row is red the PR is
red regardless, so the remaining rows only hold slots other runs need.
Push, workflow_dispatch and cron runs keep fail-fast off: main and the
manual sweeps are where the full failure picture is wanted.
The expression reads github.event_name, which inside a reusable
workflow is the caller's event, so both calls see the PR event of the
entry workflow. A red chunk already stops the later ones through
chunk_gate, and done reports the run red. Cancelled rows still run
their always() upload steps, and the failed-run retrigger selects
cancelled roles by default, so a follow-up rerun covers them.
CodeQL keeps fail-fast off: one failing language would cancel the
analysis of the others and lose findings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The mirror wiped each host's mesh block, so the write that follows minted a
fresh keypair for everyone and the converge re-keyed tunnels the deploy was
already running services over. The NFS state mount rides the data mesh and is
soft in CI, so a re-handshake dropped it; the second pass then found local
data under /var/lib/infinito and refused to mount over it.
Production never mirrors host_vars and mounts hard, so this only removes a
scenario the harness invented for itself.
The mirror copies one host's file over every other so a rotation reaches all
of them, which is right for a shared secret and wrong for anything a
generator writes per host: the copy hands every node the source's identity.
--mirror-keep names the subtrees that belong to the destination and are put
back after the copy. Absent, the mirror behaves exactly as before.
Closing a PR left "Cancel: PR Runs on Close" (run 36385268627) on
pending for hours. The canceller joins the PR orchestrator's
concurrency group, so GitHub cancelled the orchestrator run within a
second. But a cancel re-evaluates the job-level if: of every live job
and spares each one whose condition still holds. The deploy chunks
started with always(), which always holds: test-workspace died at
06:11:59, while Chunk 0 kept deploying and its ~60 queued matrix jobs
kept waiting for runners. The group stays occupied until the occupant
is terminal, so the canceller parked behind the very run it had
already cancelled.
The chunks, test-runner-smoke, the fork-prereqs gate and the release
job now guard with !cancelled(). Outside a cancel it matches always():
they still run after a failed or skipped need. On a cancel it turns
false and the job is cancelled with the run.
done and report-main-failures keep always(). They exist to report a
cancelled run, and a done skipped on cancel would count as a passing
required check.
The new lint walks every workflow whose concurrency cancels in
progress, plus everything it calls transitively, and rejects a
job-level always() outside that allowlist. It found the release job,
which a line-based grep had missed. It also fails when an allowlisted
job stops needing the exemption, and when its scope no longer reaches
the deploy workflows, so it cannot go silently empty. Against the
pre-fix tree it reports all six offenders.
Behaviour change: manually cancelling a release run now stops the
retag too. cron-release-highest re-dispatches the release.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A node is pointed at its mesh address; the controller keeps the connection it
runs the play with. The step had no test, and this entry has now bitten twice.