dejima has no release channel - it updates by git pull - so "a newer version is available" means the branch this checkout already tracks has commits this checkout does not. bin/dejima-update-check asks the remote for that branch's tip and says so, once, before the image build rather than after it, since an operator who wants the newer dejima would rather know before waiting on a build of the one they have.
It is advisory. A check that cannot answer does not stop a launch, and [core] update_check = off stops it making the request at all.
The part that needed care
Reaching the remote by name would have been the obvious implementation and is the wrong one. git ls-remote origin reads the checkout's own config for how to get there, and core.sshCommand, credential.helper and url.<base>.insteadOf each name a command git then runs on the host. That is measured, not assumed - by remote name a repo-local core.sshCommand executes, and by URL from outside the repository it does not.
That matters here specifically because dejima mounts its own checkout writable when that checkout is the workspace, which is how this repository gets worked on. A session that wrote .git/config there would otherwise get host execution out of the next launch. It is the same class as the core.hooksPath route docs/security.md already describes and accepts, with one difference worth the extra work: hooksPath waits for the operator to run a git command, and a check that runs at every startup waits for nothing.
So the URL is read from the config, checked, and handed to a git started outside the repository. protocol.allow holds the transports to ones that cannot name a command, and a URL that could be read as an option, or that names a remote helper, is refused rather than passed on. What still crosses is the URL itself, which the same write can repoint - that buys one outbound request and no execution, and the audit reports the config change it took.
Since dejima's argument is about what leaves and through which control, a request dejima makes on its own account is disclosed under its own heading in docs/security.md rather than left to be discovered.
Behaviour
The remote is asked at most once an hour. The answer it gave is compared against HEAD on every launch, so the notice appears each time until you pull and then stops immediately rather than an hour later - throttling the notice instead of the network call would mean being told once and then not again. A failed lookup is recorded as well, or a checkout with no route to its remote pays the timeout on every single launch.
Silent when current or ahead, when HEAD is detached, when the branch has no counterpart on the remote, and whenever the remote cannot be reached.
The commit count is given only when the commits are actually present locally. When the remote's tip has never been fetched - the common case - counting what it can reach would report the commits it already has rather than the ones it is missing, so it says there are commits without inventing a number.
Tests
Ten in config.bats, against a real checkout and a real remote, neither of them this repository. No git push anywhere in the fixture: dejima's own forge guard refuses a push to a path, so a fixture built that way passes on a host and fails inside a sandbox.
Four are mutation-checked against a deliberately broken implementation. Two notes on getting those right rather than merely green:
The security test first asserted on output, and the mutation that reaches the remote by name passed it - the tool sends git's stderr to /dev/null, so the assertion held whether the command ran or not. It now observes a filesystem side effect. It also asserts the control, so that if a future git stops honouring a repository's core.sshCommand the test says the threat went away rather than reading as a mitigation working.
A separate test covers a branch whose name differs from its upstream. That was a real bug in the first draft - it looked up branch.<name>.remote and then asked for refs/heads/<local branch name>, which on a git worktree add -b x origin/development checkout asks for a branch that does not exist. An empty answer is indistinguishable from being up to date, so the check went silent on exactly the branches someone developing dejima stands on. The fixture would not have caught it, since both names were the same there.
Testing
All seven confined suites pass: config 172, static 388, audit 164, providers 94, project 141, remote 109, proxy 32. shellcheck clean. bin/dejima and dejima-config-lib.sh both changed, which is why the whole set ran.
test/runtime.bats needs Docker and was not run locally. Nothing here touches the container, the mounts or the firewall - the tool runs entirely on the host, before the container exists.
No new CI destination: the fixture's remote is a local path, so nothing added here reaches the network from a runner.
**Summary generated by LLM agent**
dejima has no release channel - it updates by `git pull` - so "a newer version is available" means the branch this checkout already tracks has commits this checkout does not. `bin/dejima-update-check` asks the remote for that branch's tip and says so, once, before the image build rather than after it, since an operator who wants the newer dejima would rather know before waiting on a build of the one they have.
It is advisory. A check that cannot answer does not stop a launch, and `[core] update_check = off` stops it making the request at all.
## The part that needed care
Reaching the remote by name would have been the obvious implementation and is the wrong one. `git ls-remote origin` reads the checkout's own config for how to get there, and `core.sshCommand`, `credential.helper` and `url.<base>.insteadOf` each name a command git then runs on the host. That is measured, not assumed - by remote name a repo-local `core.sshCommand` executes, and by URL from outside the repository it does not.
That matters here specifically because dejima mounts its own checkout writable when that checkout is the workspace, which is how this repository gets worked on. A session that wrote `.git/config` there would otherwise get host execution out of the *next* launch. It is the same class as the `core.hooksPath` route `docs/security.md` already describes and accepts, with one difference worth the extra work: `hooksPath` waits for the operator to run a git command, and a check that runs at every startup waits for nothing.
So the URL is read from the config, checked, and handed to a git started outside the repository. `protocol.allow` holds the transports to ones that cannot name a command, and a URL that could be read as an option, or that names a remote helper, is refused rather than passed on. What still crosses is the URL itself, which the same write can repoint - that buys one outbound request and no execution, and the audit reports the config change it took.
Since dejima's argument is about what leaves and through which control, a request dejima makes on its own account is disclosed under its own heading in `docs/security.md` rather than left to be discovered.
## Behaviour
The remote is asked at most once an hour. The answer it gave is compared against HEAD on *every* launch, so the notice appears each time until you pull and then stops immediately rather than an hour later - throttling the notice instead of the network call would mean being told once and then not again. A failed lookup is recorded as well, or a checkout with no route to its remote pays the timeout on every single launch.
Silent when current or ahead, when HEAD is detached, when the branch has no counterpart on the remote, and whenever the remote cannot be reached.
The commit count is given only when the commits are actually present locally. When the remote's tip has never been fetched - the common case - counting what it can reach would report the commits it already has rather than the ones it is missing, so it says there are commits without inventing a number.
## Tests
Ten in `config.bats`, against a real checkout and a real remote, neither of them this repository. No `git push` anywhere in the fixture: dejima's own forge guard refuses a push to a path, so a fixture built that way passes on a host and fails inside a sandbox.
Four are mutation-checked against a deliberately broken implementation. Two notes on getting those right rather than merely green:
The security test first asserted on output, and the mutation that reaches the remote by name *passed* it - the tool sends git's stderr to `/dev/null`, so the assertion held whether the command ran or not. It now observes a filesystem side effect. It also asserts the control, so that if a future git stops honouring a repository's `core.sshCommand` the test says the threat went away rather than reading as a mitigation working.
A separate test covers a branch whose name differs from its upstream. That was a real bug in the first draft - it looked up `branch.<name>.remote` and then asked for `refs/heads/<local branch name>`, which on a `git worktree add -b x origin/development` checkout asks for a branch that does not exist. An empty answer is indistinguishable from being up to date, so the check went silent on exactly the branches someone developing dejima stands on. The fixture would not have caught it, since both names were the same there.
## Testing
All seven confined suites pass: config 172, static 388, audit 164, providers 94, project 141, remote 109, proxy 32. `shellcheck` clean. `bin/dejima` and `dejima-config-lib.sh` both changed, which is why the whole set ran.
`test/runtime.bats` needs Docker and was not run locally. Nothing here touches the container, the mounts or the firewall - the tool runs entirely on the host, before the container exists.
No new CI destination: the fixture's remote is a local path, so nothing added here reaches the network from a runner.
dejima updates by `git pull`, so a launch can tell you there is a newer one
by asking the remote your checkout already tracks for the tip of the branch
it already follows. One line, before the image build rather than after it,
and advisory: a check that cannot answer does not stop a launch.
The remote is reached by URL, from outside the repository, and never by
remote name. `git ls-remote origin` reads the checkout's own config for how
to get there, and core.sshCommand, credential.helper and url.<base>.insteadOf
each name a command git then runs on the host - measured, not assumed. dejima
mounts its own checkout writable when that checkout is the workspace, so a
session writing .git/config there would otherwise get host execution out of
the next launch. Same class as the core.hooksPath route docs/security.md
already describes, with one difference: hooksPath waits for the operator to
run a git command, and a startup check waits for nothing. protocol.allow
holds the transports to ones that cannot name a command, and a URL that
could be read as an option is refused rather than passed on.
What still crosses is the URL, which the same write can repoint. That buys
one outbound request and no execution, and the audit reports the config
change it took. Stated in docs/security.md under its own heading, because a
request dejima makes on its own account is the one an operator is least
expecting.
The remote is asked at most hourly; the answer it gave is compared against
HEAD on every launch, so the notice appears each time until you pull and then
stops immediately rather than an hour later. A failed lookup is recorded too,
or a checkout with no route to its remote pays the timeout every launch.
[core] update_check = off stops it entirely, and then dejima contacts nothing
of its own accord.
Tests: ten in config.bats against a real checkout and a real remote, neither
of them this repository and no push anywhere - dejima's own forge guard
refuses a push to a path, so a fixture built that way would pass on a host
and fail inside a sandbox.
Four are mutation-checked. The security one observes a filesystem side effect
rather than a message, because the tool sends git's stderr to /dev/null and an
output assertion held whether the command ran or not; it also asserts the
control, so it cannot pass by the threat quietly going away.
branderson
approved these changes 2026-08-26 17:52:07 -07:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary generated by LLM agent
dejima has no release channel - it updates by
git pull- so "a newer version is available" means the branch this checkout already tracks has commits this checkout does not.bin/dejima-update-checkasks the remote for that branch's tip and says so, once, before the image build rather than after it, since an operator who wants the newer dejima would rather know before waiting on a build of the one they have.It is advisory. A check that cannot answer does not stop a launch, and
[core] update_check = offstops it making the request at all.The part that needed care
Reaching the remote by name would have been the obvious implementation and is the wrong one.
git ls-remote originreads the checkout's own config for how to get there, andcore.sshCommand,credential.helperandurl.<base>.insteadOfeach name a command git then runs on the host. That is measured, not assumed - by remote name a repo-localcore.sshCommandexecutes, and by URL from outside the repository it does not.That matters here specifically because dejima mounts its own checkout writable when that checkout is the workspace, which is how this repository gets worked on. A session that wrote
.git/configthere would otherwise get host execution out of the next launch. It is the same class as thecore.hooksPathroutedocs/security.mdalready describes and accepts, with one difference worth the extra work:hooksPathwaits for the operator to run a git command, and a check that runs at every startup waits for nothing.So the URL is read from the config, checked, and handed to a git started outside the repository.
protocol.allowholds the transports to ones that cannot name a command, and a URL that could be read as an option, or that names a remote helper, is refused rather than passed on. What still crosses is the URL itself, which the same write can repoint - that buys one outbound request and no execution, and the audit reports the config change it took.Since dejima's argument is about what leaves and through which control, a request dejima makes on its own account is disclosed under its own heading in
docs/security.mdrather than left to be discovered.Behaviour
The remote is asked at most once an hour. The answer it gave is compared against HEAD on every launch, so the notice appears each time until you pull and then stops immediately rather than an hour later - throttling the notice instead of the network call would mean being told once and then not again. A failed lookup is recorded as well, or a checkout with no route to its remote pays the timeout on every single launch.
Silent when current or ahead, when HEAD is detached, when the branch has no counterpart on the remote, and whenever the remote cannot be reached.
The commit count is given only when the commits are actually present locally. When the remote's tip has never been fetched - the common case - counting what it can reach would report the commits it already has rather than the ones it is missing, so it says there are commits without inventing a number.
Tests
Ten in
config.bats, against a real checkout and a real remote, neither of them this repository. Nogit pushanywhere in the fixture: dejima's own forge guard refuses a push to a path, so a fixture built that way passes on a host and fails inside a sandbox.Four are mutation-checked against a deliberately broken implementation. Two notes on getting those right rather than merely green:
The security test first asserted on output, and the mutation that reaches the remote by name passed it - the tool sends git's stderr to
/dev/null, so the assertion held whether the command ran or not. It now observes a filesystem side effect. It also asserts the control, so that if a future git stops honouring a repository'score.sshCommandthe test says the threat went away rather than reading as a mitigation working.A separate test covers a branch whose name differs from its upstream. That was a real bug in the first draft - it looked up
branch.<name>.remoteand then asked forrefs/heads/<local branch name>, which on agit worktree add -b x origin/developmentcheckout asks for a branch that does not exist. An empty answer is indistinguishable from being up to date, so the check went silent on exactly the branches someone developing dejima stands on. The fixture would not have caught it, since both names were the same there.Testing
All seven confined suites pass: config 172, static 388, audit 164, providers 94, project 141, remote 109, proxy 32.
shellcheckclean.bin/dejimaanddejima-config-lib.shboth changed, which is why the whole set ran.test/runtime.batsneeds Docker and was not run locally. Nothing here touches the container, the mounts or the firewall - the tool runs entirely on the host, before the container exists.No new CI destination: the fixture's remote is a local path, so nothing added here reaches the network from a runner.