diff --git a/docs/openbao-public-listener-transition.md b/docs/openbao-public-listener-transition.md index 0ddd776..a4317c6 100644 --- a/docs/openbao-public-listener-transition.md +++ b/docs/openbao-public-listener-transition.md @@ -55,7 +55,9 @@ Warden's contained child inherits the caller's cwd, so a relative `scripts/...` path raises OSError, prints `attended command could not start`, and revokes a successful login without applying the callback. Do not run the owner command directly with a persistent token. The wrapper also supplies the WSL browser -launcher when native `xdg-open` is absent. +launcher when native `xdg-open` is absent, and pins `BAO_ADDR` to +`http://127.0.0.1:18200`. After public Ingress retraction, CLI login against +`https://bao.coulomb.social` fails before command handoff. ## Guarded sequence diff --git a/scripts/openbao-attended-exec.py b/scripts/openbao-attended-exec.py index 6255e68..78950a8 100755 --- a/scripts/openbao-attended-exec.py +++ b/scripts/openbao-attended-exec.py @@ -23,14 +23,25 @@ def reviewed_command(args): return [str(command), *args[1:]] -def main(): - args = reviewed_command(sys.argv[1:]) +TUNNEL = 'http://127.0.0.1:18200' + + +def contained_env(): env = os.environ.copy() + env['BAO_ADDR'] = TUNNEL + env['VAULT_ADDR'] = TUNNEL + env.pop('BAO_TOKEN', None) + env.pop('VAULT_TOKEN', None) if not any(shutil.which(x) for x in ('xdg-open', 'x-www-browser', 'www-browser')): if not Path('/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe').is_file(): raise SystemExit('No supported attended browser launcher is available') env['PATH'] = str(Path(__file__).resolve().parent / 'operator-browser') + os.pathsep + env.get('PATH', '') - os.execvpe('warden', ['warden', 'access', 'openbao-platform-admin-login', '--exec', '--', *args], env) + return env + + +def main(): + args = reviewed_command(sys.argv[1:]) + os.execvpe('warden', ['warden', 'access', 'openbao-platform-admin-login', '--exec', '--', *args], contained_env()) if __name__ == '__main__': diff --git a/tests/test_openbao_operator_loopback_callback.py b/tests/test_openbao_operator_loopback_callback.py index 5f7d975..7af1aa3 100644 --- a/tests/test_openbao_operator_loopback_callback.py +++ b/tests/test_openbao_operator_loopback_callback.py @@ -87,6 +87,21 @@ def test_receipt_records_apply_and_failure(tmp_path, monkeypatch): assert json.loads(failed.read_text())['status'] == 'bao_write_failed' +def test_attended_wrapper_pins_operator_tunnel_address(monkeypatch): + spec = importlib.util.spec_from_file_location( + 'attended', Path(__file__).resolve().parents[1] / 'scripts/openbao-attended-exec.py') + wrapper = importlib.util.module_from_spec(spec) + spec.loader.exec_module(wrapper) + monkeypatch.setenv('BAO_ADDR', 'https://bao.coulomb.social') + monkeypatch.setenv('VAULT_ADDR', 'https://bao.coulomb.social') + monkeypatch.setenv('BAO_TOKEN', 'must-not-propagate') + env = wrapper.contained_env() + assert env['BAO_ADDR'] == wrapper.TUNNEL + assert env['VAULT_ADDR'] == wrapper.TUNNEL + assert 'BAO_TOKEN' not in env + assert 'VAULT_TOKEN' not in env + + def test_attended_wrapper_requires_absolute_existing_executable(tmp_path, monkeypatch): import os spec = importlib.util.spec_from_file_location( diff --git a/workplans/RPF-WP-0042-informed-decision-sitting-requester.md b/workplans/RPF-WP-0042-informed-decision-sitting-requester.md index bf89d4e..63fb3fe 100644 --- a/workplans/RPF-WP-0042-informed-decision-sitting-requester.md +++ b/workplans/RPF-WP-0042-informed-decision-sitting-requester.md @@ -47,3 +47,8 @@ Operator approved CCR-2026-0026/0027 on 2026-09-15. Source registration is in `key-cape/config/service-clients.example.yaml`. Live apply is the silent helper `scripts/provision-sitting-requester.sh` through `openbao-attended-exec.py`. No sitting POST until exchange proof exists. Do not widen CCR-2026-0024/0025. + +2026-09-15 attended attempt failed before command handoff: +`revocation could not be confirmed`. Public Ingress is already absent, and the +shell still had `BAO_ADDR=https://bao.coulomb.social`. The wrapper now pins the +operator tunnel. Retry that same helper; do not treat this attempt as custody.