the-custodian/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/implementation.patch
codex 0c82a71aed
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s
Reject generated-only commits / generated-commit-guard (push) Successful in 14s
docs: preserve reviewed NetKingdom session artifacts
2026-11-01 22:49:34 +01:00

198 lines
11 KiB
Diff

--- a/sso-mfa/k8s/keycape/keycape-client.py
+++ b/sso-mfa/k8s/keycape/keycape-client.py
@@ -200,10 +200,10 @@
def check_config(binary, config_text, env_names, policy_store):
"""KeyCape's startup checks on one registry: names and messages only, no values."""
- command = [binary, 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
+ command = [str(Path(binary).resolve()), 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
if policy_store:
command.append('--policy-store')
- result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60)
+ result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60, env={})
report = json.loads(result.stdout)
require(isinstance(report, dict) and isinstance(report.get('valid'), bool)
and report['valid'] == (result.returncode == 0), 'check_config_output_shape')
--- /dev/null
+++ b/sso-mfa/k8s/keycape/test_keycape_checker_environment.py
@@ -0,0 +1,66 @@
+"""Opt-in real-checker boundary regressions; no production inputs or checker stub.
+
+Set KEYCAPE_TEST_CHECKER_BINARY and KEYCAPE_TEST_CHECKER_SHA256 to an independently
+built artifact. Ordinary helper-only runs skip this class when none is supplied.
+"""
+from contextlib import chdir
+import hashlib
+import os
+from pathlib import Path
+import tempfile
+import unittest
+from unittest.mock import patch
+
+from test_keycape_client import kc
+
+VALID = '''issuer: https://issuer.example.invalid
+port: 8080
+privateKeyPem: /nonexistent/synthetic-key.pem
+clients:
+ - clientId: synthetic-client
+ displayName: Synthetic client
+ redirectUris: [https://client.example.invalid/cb]
+ allowedScopes: [openid]
+ grantTypes: [authorization_code]
+ clientType: public
+'''
+
+
+class RealCheckerEnvironmentTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ source = os.environ.get('KEYCAPE_TEST_CHECKER_BINARY')
+ if not source:
+ raise unittest.SkipTest('exact real checker artifact not supplied')
+ data = Path(source).read_bytes()
+ expected = os.environ.get('KEYCAPE_TEST_CHECKER_SHA256')
+ if expected != 'sha256:' + hashlib.sha256(data).hexdigest():
+ raise ValueError('real checker artifact digest differs')
+ cls.directory = tempfile.TemporaryDirectory()
+ cls.addClassCleanup(cls.directory.cleanup)
+ cls.binary = Path(cls.directory.name) / 'keycape'
+ cls.binary.write_bytes(data)
+ cls.binary.chmod(0o700)
+
+ def check(self, raw, binary=None):
+ return kc.check_config(str(binary or self.binary), raw, [], False)
+
+ def test_ambient_overrides_cannot_reject_supplied_valid_config(self):
+ with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://polluted.example.invalid',
+ 'KEYCAPE_BROWSER_LOGOUT_URL': 'http://polluted.example.invalid/logout'}):
+ self.assertTrue(self.check(VALID)['valid'])
+
+ def test_ambient_overrides_cannot_admit_supplied_invalid_config(self):
+ invalid = VALID + 'accountPortalURL: http://invalid.example.invalid\nbrowserLogoutURL: http://invalid.example.invalid/logout\n'
+ with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'https://polluted.example.invalid',
+ 'KEYCAPE_BROWSER_LOGOUT_URL': 'https://polluted.example.invalid/logout'}):
+ self.assertFalse(self.check(invalid)['valid'])
+
+ def test_relative_selected_artifact_is_not_replaced_by_path_decoy(self):
+ decoy = Path(self.directory.name) / 'decoy'
+ decoy.mkdir()
+ trap = decoy / 'keycape'
+ trap.write_text('#!/bin/sh\nexit 27\n')
+ trap.chmod(0o700)
+ with chdir(self.directory.name), patch.dict(os.environ, {'PATH': str(decoy)}):
+ self.assertTrue(self.check(VALID, binary=Path('keycape'))['valid'])
--- a/sso-mfa/k8s/keycape/registration-adapter.md
+++ b/sso-mfa/k8s/keycape/registration-adapter.md
@@ -13,7 +13,12 @@
and its complete local helper import closure together with the exact reviewed
`check-config` executable digest. Changing that digest changes the descriptor
source pin and descriptor bytes, requiring independently reviewed admission.
-The fixed executable is rehashed before each attempt. The caller constructs the
+The fixed executable is rehashed before each attempt. The helper resolves the
+selected file to an absolute executable path and supplies an empty checker
+environment, so ambient PATH and KeyCape URL overrides cannot select another
+executable or alter validation of supplied bytes. Path resolution does not
+establish immutable storage or eliminate replacement races; executable custody
+and native runtime admission remain separate requirements. The caller constructs the
proposed invocation from exact source inputs, installs the fixed adapter once,
and calls normal `Executor.admit`; independently derived admission must match.
No callable replacement after admission is required or supported by this recipe.
--- a/workplans/NK-WP-0048-security-case-types.md
+++ b/workplans/NK-WP-0048-security-case-types.md
@@ -323,7 +323,7 @@
```task
id: NK-WP-0048-T06
status: wait
-depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
+depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, NK-WP-0048-T18, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
@@ -734,10 +734,11 @@
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
-independently owns global provenance/admission. Next action: identify the exact
-reviewed checker executable artifact and native owner evidence/session callback
-contracts, then assemble the complete step descriptor set and a proposed explicit
-type version selection. T13's retained descriptor pins a synthetic checker only.
+independently owns global provenance/admission. Next action: obtain review of the
+exact checker source/artifact proposed by T17 and identify native owner evidence/
+session callback contracts, then assemble the complete step descriptor set and
+a proposed explicit type version selection. T13's historical descriptor retains
+its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
@@ -746,3 +747,70 @@
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.
+
+## T17 — Build and exercise the exact offline config-checker artifact
+
+```task
+id: NK-WP-0048-T17
+status: done
+priority: high
+```
+
+Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
+existing `key-cape/src/cmd/keycape` command from published clean source revision
+`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
+`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
+CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
+read-only module selection. The full retained closure pins 108 source files,
+9876 toolchain files and ten used module source directories. Three isolated
+build locations produced identical 13633409-byte artifacts:
+`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
+
+Eight disposable checks use the actual executable and maintained helper/kernel,
+with complete synthetic configs and a minimal environment: add/created-only
+removal, normal pinned producer admission, rollback, lost-readback uncertainty,
+stale revisions, substituted checker refusal, invalid startup config and missing
+secret-env refusal. No checker stub or producer-pin override remains. The
+artifact-bound descriptor proposal is
+`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
+Evidence: Custodian
+`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
+The first two build attempts succeeded but their closure collection failed;
+both failures are retained, as is refusal of the older incomplete synthetic
+fixture. No production config, credentials or cluster mutation was used.
+
+This extracts completed build/fixture scope from T16 without claiming KeyCape
+owner endorsement, native checker selection/storage custody, acknowledged type
+integration, native evidence/session callbacks or attended browser acceptance.
+T16 retains exact owner review and composition/admission scope. The attended
+registration procedure remains unexercised.
+
+## T18 — Make checker input and executable selection deterministic
+
+```task
+id: NK-WP-0048-T18
+status: done
+priority: high
+```
+
+Completed 2026-10-16, isolated source correction and real-artifact fixture proof
+only; independent integration/publication remain separately recorded. KeyCape
+source review identified non-secret `KEYCAPE_ACCOUNT_PORTAL_URL` and
+`KEYCAPE_BROWSER_LOGOUT_URL` overrides in config.Load. The original helper
+inherited ambient environment, allowing valid supplied configuration to be
+rejected or invalid supplied URL configuration to be admitted. A bare relative
+checker filename could also be replaced by PATH lookup after the adapter had
+hashed the selected local file. All three counterexamples are retained against
+the unchanged predecessor.
+
+The existing helper now resolves the selected checker file to an absolute path
+and passes an explicit empty subprocess environment. Three opt-in real-checker
+regressions verify both URL-verdict directions and relative-file selection under
+a non-checker PATH decoy. All 39 affected checks pass with the exact supplied
+artifact, as do 11 real-artifact helper/kernel scenarios. Source evidence:
+Custodian `docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/`.
+Historical descriptors remain retained; the helper correction changes successor
+source-composition pins and requires their separate review/admission. An absolute
+path is not immutable custody and does not eliminate file replacement races;
+T16 retains native executable storage/custody and callback/type admission.
+No production configuration, credentials or cluster mutation was used.