Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,17 @@ Before planning/executing, read
for script routes, effects, trusted handoff, transport, push, and verification.
Run and review the planner first; then bind publication authority as above to
the reviewed `ready` plan's push before invoking the executor.
The adapter uses the host-compatible Git configuration profile by default and
supports an explicit `VERSIONKEEPING_GIT_CONFIG_PROFILE=hardened` profile for
closed-environment publication. Both profiles reject command-valued local/worktree
settings (including signing programs) and unsafe includes without exposing their
values. The hardened profile additionally disables implicit commit/tag/push signing
and closes configuration across all scopes; it permits only HTTPS, SSH, or
ancestry-guarded local endpoints.
For the guarded authenticated HTTPS contract—no ambient executable Git
configuration, one trusted provider, and a fail-closed provider boundary—set
`VERSIONKEEPING_GIT_CONFIG_PROFILE=hardened` before planning and keep it selected
through execution. The profile is reviewed plan identity; selecting another profile
requires a fresh plan and review. The adapter otherwise uses `host-compatible` by
default. That compatibility profile may inherit effective host configuration and
providers, so it is outside the guarded contract. Both profiles reject command-valued
local/worktree settings (including signing programs) and unsafe includes without
exposing their values. The hardened profile additionally disables implicit
commit/tag/push signing and closes configuration across all scopes; it permits only
HTTPS, SSH, or ancestry-guarded local endpoints.

Follow its typed gates exactly. Ordinary publication never deletes a remote ref;
the separate terminal route requires verified merge and explicit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,18 @@ remote name is discovery metadata, not a publication actuator.
Ordinary publication uses the `host-compatible` Git configuration profile by
default. It inherits the effective host Git configuration and credential
helpers visible to the harness, while retaining endpoint, ref, lease, prompt,
and post-push verification gates. Set
`VERSIONKEEPING_GIT_CONFIG_PROFILE=hardened` for the explicit closed profile.
That profile clears global and system configuration and permits only the
validated platform provider described below. The selected profile is included
in the reviewed destination configuration digest; changing it requires a fresh
plan and review.

The ordinary publication executor supports noninteractive HTTPS credentials on
and post-push verification gates. This weaker, harness-trusting profile is
outside the guarded authenticated HTTPS contract; that distinction is a route
boundary, not an assertion that it provides hardened isolation. When the
publication needs no ambient executable Git configuration, one trusted
provider, and a fail-closed provider boundary, set
`VERSIONKEEPING_GIT_CONFIG_PROFILE=hardened` before planning and keep it
selected through execution. That profile clears global and system
configuration and permits only the validated platform provider described
below. The selected profile is included in the reviewed destination
configuration digest; changing it requires a fresh plan and review.

The hardened publication profile supports noninteractive HTTPS credentials on
modern macOS, Linux, and Windows through closed platform-provider sets:

- macOS uses `git-credential-osxkeychain` from the trusted system Git exec
Expand Down Expand Up @@ -140,17 +144,21 @@ variables are also removed. This prevents a narrower Git configuration or
process environment from weakening TLS beneath the authenticated endpoint.
Rejected setting values are never read into diagnostics.

Credential bytes travel only between Git and the system helper through Git's
credential protocol: they are never read by the planner or executor and never
enter arguments, diagnostics, plans, receipts, or repository configuration.
SSH and ancestry-guarded local endpoints do not enable a credential helper and
retain their existing contracts.
Credential-provider response bytes remain between Git and the system helper.
The executor sends credential-preflight stdout and stderr to the null device
and decides availability only from the return code; it never receives or
inspects provider response bytes. Credential bytes never enter arguments,
diagnostics, plans, receipts, or repository configuration. SSH and
ancestry-guarded local endpoints do not enable a credential helper and retain
their existing contracts.

## Terminal Remote-Ref Deletion

Remote-ref deletion is a separate planner and executor surface owned by this
skill. It is not an ordinary publication update and must not be supplied to
`execute_git_publication.py`. Before planning, the deletion request requires a
`execute_git_publication.py`. It is outside this guarded authenticated HTTPS
claim and must establish any credential/profile claim under its own contract.
Before planning, the deletion request requires a
verified merge outcome plus explicit repository and operator authorization. Each
authorization repeats and exactly binds the remote, full `refs/heads/...` ref,
and `expected_target_sha`; the verified merged source SHA must equal that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1224,8 +1224,8 @@ def ensure_https_credentials(self, endpoint: str) -> None:
env=self.env,
input=query,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=self.timeout_seconds,
check=False,
)
Expand All @@ -1234,7 +1234,7 @@ def ensure_https_credentials(self, endpoint: str) -> None:
"HTTPS_CREDENTIALS_UNAVAILABLE",
profile=self.config_profile,
) from error
if result.returncode != 0 or not result.stdout.strip():
if result.returncode != 0:
raise PolicyGate(
"HTTPS_CREDENTIALS_UNAVAILABLE",
profile=self.config_profile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,20 @@ def execute_repository(
expected_fingerprint=destination["endpoint_fingerprint"],
observed_fingerprint=endpoint_fingerprint,
)
if repo.config_profile != destination["config_profile"]:
early_selection = dict(selection)
early_selection["default_branch_ref"] = destination[
"default_branch_ref"
]
early_selection["config_profile"] = repo.config_profile
raise PolicyGate(
"REVIEWED_CONFIG_CHANGED",
expected_digest=destination["config_digest"],
observed_digest=_config_digest(
early_selection,
endpoint_fingerprint,
),
)
repo.enable_https_credentials(endpoint)
default_branch_ref = _probe_default_branch(repo, endpoint, object_format)
if default_branch_ref != destination["default_branch_ref"]:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
8 changes: 4 additions & 4 deletions release/plugin-content-locks/versionkeeping.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/SKILL.md": {
"sha256": "12c8e6cff200774dce7d371448e96e4d46109c5b1989378af16f32383494670f",
"sha256": "01ca4151554adad2f8ff4d734f9877c66cce0d5f3f97b2844b454053dd6da846",
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/agents/openai.yaml": {
Expand All @@ -35,7 +35,7 @@
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/references/publication-execution.md": {
"sha256": "3eac694a5de44fff942a6ee5d11defd867670abf6c2230fbf2839172f02b8f47",
"sha256": "e2dedd152502037a961e3204ea30cef916d3080dbab0e0b84cdae6995e72e6ae",
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/references/terminal-cleanup.md": {
Expand All @@ -59,7 +59,7 @@
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/scripts/git_publication/adapter.py": {
"sha256": "9b6f56af71a59cdcacb8ede86fb00960c3c54fd6ffb76ae66a5bb6cff24d1640",
"sha256": "14f233a5b166d61f71e02dca9e97a71bfeff2120591ad03daadbbbe2db1f7ed2",
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/scripts/git_publication/core.py": {
Expand All @@ -71,7 +71,7 @@
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/scripts/git_publication/execution.py": {
"sha256": "2fcd07d058460a4bf1dc110b569128dfa22f21b59e6c6e8c2c81a0ca77dced99",
"sha256": "e448b37ffa9252d257aeb8204978154cf1e4404e8c3c0c4b1a1f7c3deed5bdfa",
"mode": 420
},
"skills/checkpointing-and-publishing-git-work/scripts/plan_git_publication.py": {
Expand Down
2 changes: 1 addition & 1 deletion release/provingkit/definition-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"content_identity": {
"kind": "plugin-content-lock",
"path": "release/plugin-content-locks/versionkeeping.json",
"sha256": "sha256:7cb688670ebc3a2b1ef7cc43c0fac7772903916c1fae17997692492113127143"
"sha256": "sha256:8e226e2ce5098868c76b72eeaa994710d620da2a2c80966bd69352941897b0f9"
},
"display_name": "Versionkeeping",
"distribution_kind": "agent-plugin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,43 @@ def test_https_credential_preflight_includes_repository_path(self):
"protocol=https\nhost=github.com\npath=nisavid/provingkit.git\n\n",
)

def test_https_credential_preflight_uses_only_return_code(self):
repository = object.__new__(adapter.GitRepository)
repository.git_executable = "git"
repository.path = Path("/controlled/repository")
repository.env = {}
repository.timeout_seconds = 1
repository.config_profile = "hardened"
repository.enable_https_credentials = mock.Mock()
completed = (
subprocess.CompletedProcess(
["git", "credential", "fill"], 0, "", ""
),
subprocess.CompletedProcess(
["git", "credential", "fill"], 1, None, None
),
)
with mock.patch.object(
adapter.subprocess, "run", side_effect=completed
) as run:
try:
repository.ensure_https_credentials(
"https://github.com/nisavid/provingkit.git"
)
except adapter.PolicyGate as gate:
self.fail(f"zero return code was rejected: {gate.code}")

with self.assertRaises(adapter.PolicyGate) as raised:
repository.ensure_https_credentials(
"https://github.com/nisavid/provingkit.git"
)

self.assertEqual(raised.exception.code, "HTTPS_CREDENTIALS_UNAVAILABLE")
for call in run.call_args_list:
self.assertIs(call.kwargs["check"], False)
self.assertIs(call.kwargs["stdout"], subprocess.DEVNULL)
self.assertIs(call.kwargs["stderr"], subprocess.DEVNULL)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

def test_cached_https_credentials_recheck_host_configuration(self):
repository = object.__new__(adapter.GitRepository)
repository.path = Path("/controlled/repository")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,77 @@ def test_config_change_after_review_blocks_before_push(self) -> None:
self.assertFalse(result["push_attempted"])
self.assertEqual(result["reasons"][0]["code"], "REVIEWED_CONFIG_CHANGED")

def test_reviewed_profile_change_blocks_before_external_boundaries(self) -> None:
events = []
endpoint = "https://example.invalid/repository.git"
destination = self.plan["destination"]
host_home = self.root / "host-home"
host_config_home = self.root / "host-config"
host_home.mkdir()
host_config_home.mkdir()

def record_credential_activation(_repository, _endpoint):
events.append("credential_activation")

def record_default_branch_probe(*_args, **_kwargs):
events.append("endpoint_probe")
return destination["default_branch_ref"]

def record_target_probe(*_args, **_kwargs):
events.append("endpoint_probe")
return self.start

def record_push(*_args, **_kwargs):
events.append("push")
return subprocess.CompletedProcess(["git", "push"], 0, "", "")

with mock.patch.dict(
os.environ,
{
adapter.GIT_CONFIG_PROFILE_ENV: "host-compatible",
"HOME": str(host_home),
"XDG_CONFIG_HOME": str(host_config_home),
"PATH": "/usr/bin:/bin",
},
clear=False,
):
with mock.patch.object(
execution,
"_endpoint",
return_value=(endpoint, destination["endpoint_fingerprint"]),
):
with mock.patch.object(
execution.GitRepository,
"enable_https_credentials",
autospec=True,
side_effect=record_credential_activation,
):
with mock.patch.object(
execution,
"_probe_default_branch",
side_effect=record_default_branch_probe,
):
with mock.patch.object(
execution,
"_probe_ref",
side_effect=record_target_probe,
):
with mock.patch.object(
execution,
"_run_endpoint",
side_effect=record_push,
):
result = execute(self.repo, self.plan)

self.assertEqual(result["status"], "blocked")
self.assertFalse(result["push_attempted"])
self.assertEqual(result["reasons"][0]["code"], "REVIEWED_CONFIG_CHANGED")
self.assertEqual(events, [])
self.assertEqual(
git(self.remote, "rev-parse", "refs/heads/topic"),
self.start,
)

def test_target_lease_change_after_review_blocks_before_push(self) -> None:
remote_only = commit(self.repo, "remote-only")
git(self.repo, "push", "publish", f"{remote_only}:refs/heads/topic")
Expand Down
Loading