Bind wrapped issuance to native metadata and preserve immutable recipient attempts
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
58129de8f1
commit
0db6adef69
8 changed files with 288 additions and 46 deletions
|
|
@ -235,3 +235,12 @@ secrets-engine verify whynot-design-npm-publish --field npm_token --positive --n
|
|||
# 6. publish with the token injected into the child only
|
||||
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish
|
||||
```
|
||||
|
||||
Wrapped handoff files must be fresh and inside an owned private directory outside
|
||||
Git. Existing files, symlinks and shared directories refuse before minting. A
|
||||
failed or uncertain mint leaves its private reservation for owner reconciliation;
|
||||
never delete it merely to retry. The backend must return the exact native KV-v2
|
||||
data/AppRole creation path, requested bounded TTL and an accessor. Handoff-file
|
||||
creation verifies those native fields; a requested path alone is not evidence.
|
||||
These checks do not enable production wrapping or the separate execute-ccr
|
||||
metadata entitlement.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"schema": "wrapped-issuer-source-validation/v1",
|
||||
"task": "SECRETS-WP-0013-T01",
|
||||
"captured_at": "2026-10-11T22:07:09.996760+00:00",
|
||||
"source_sha256": {
|
||||
"src/secrets_engine/openbao.py": "c1d17ca30367fd2c856927e8ce4021599a992c6c22509f82a6d8d4588ea46199",
|
||||
"src/secrets_engine/wrap.py": "a37c491a93c8803015d2f1fca6ea502b8da28a9279900404d8f9ebf06a0c0a70",
|
||||
"tests/test_wrap.py": "40b4511f6f5c2515e8ff70095e8239a4f589a97697fa1827298ec3be89ba57d4",
|
||||
"tests/test_wrapped_issuer_native.py": "e24d86da4e9b0c971aa39c81f34c1bced76d3def1727ee38fe69a3769c80e5c1"
|
||||
},
|
||||
"full_suite": {
|
||||
"collected": 654,
|
||||
"passed": 653,
|
||||
"skipped": 1,
|
||||
"exit": 0,
|
||||
"execution": "isolated tracked-source Git checkout, real filesystem ownership, disposable local OpenBao"
|
||||
},
|
||||
"final_wrap_and_native_tests": {
|
||||
"passed": 24,
|
||||
"exit": 0
|
||||
},
|
||||
"native_local_semantics": {
|
||||
"creation_path": "secret/data/d3-wrap-fixture",
|
||||
"ttl_seconds": 60,
|
||||
"first_unwrap_success": true,
|
||||
"consumed_token_replay": "specific native HTTP400 wrapping-token-invalid denial"
|
||||
},
|
||||
"production_lane_proven": false,
|
||||
"production_authority_enabled": false,
|
||||
"raw_secret_values_in_receipt": false,
|
||||
"negative_cases": [
|
||||
"changed/missing native path",
|
||||
"wrong/missing/noninteger TTL",
|
||||
"missing accessor",
|
||||
"unwrapped material alongside wrap",
|
||||
"overwrite",
|
||||
"symlink leaf/ancestor",
|
||||
"shared parent",
|
||||
"uncertain mint remint",
|
||||
"concurrent duplicate",
|
||||
"reservation replacement"
|
||||
],
|
||||
"preparation_failures": [
|
||||
{
|
||||
"kind": "missing local clock module in isolated test collection",
|
||||
"recovery": "explicit owner clock source dependency"
|
||||
},
|
||||
{
|
||||
"kind": "full sandbox suite had17 unrelated custody/path failures",
|
||||
"recovery": "created actual isolated Git worktree and reran with real filesystem ownership; no custody guard relaxed"
|
||||
}
|
||||
],
|
||||
"pending": "Independent Platform/identity/approval/PDP review, separate exact actor/path/audience delivery entitlement and estate-native proof"
|
||||
}
|
||||
|
|
@ -311,3 +311,24 @@ this change does not make them verified human representation. Existing generic
|
|||
consume receipt validation limitations remain explicitly separate. service-jwt
|
||||
readiness guard/default and ordinary actions remain unchanged. T01 stays in
|
||||
progress, T03 stays wait, WP13 stays proposed, and WP8-T06 stays open.
|
||||
|
||||
## D3 wrapped-delivery primitive and its separate entitlement
|
||||
|
||||
D3 P3=b chooses response-wrapped issuer delivery with one-time unwrap. The
|
||||
existing wrapped-handoff primitive now verifies native TTL/accessor/creation-path
|
||||
metadata and reserves a private immutable recipient file before minting. Native
|
||||
KV-v2 creation paths include the `data/` endpoint; caller-requested CLI paths
|
||||
cannot substitute for returned metadata. Failed mint/write or an altered
|
||||
reservation retains an uncertain attempt and never silently overwrites/remints.
|
||||
|
||||
A disposable local OpenBao integration proves the exact source path,60-second
|
||||
TTL, successful first unwrap and specific HTTP400 consumed-token replay denial.
|
||||
This proves backend semantics, not the estate's first delivered lane, audited
|
||||
initiating principal, consumer identity, independent owner acceptance or cleanup
|
||||
under crash/revocation. Production CLI wrapping remains fail-closed. Existing
|
||||
metadata execute-ccr contracts expressly exclude wrap/KV value delivery and cannot
|
||||
be borrowed for this purpose. A separate exact owner-admitted delivery operation
|
||||
must bind actor,path,audience,deadline,authorization/consume state,destination,
|
||||
audit,replay/crash recovery and rollback before native adoption. The prepared
|
||||
D2 repository-issuer lane remains attended until its own reviewed execution and
|
||||
independent destination/adoption proofs pass.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from __future__ import annotations
|
|||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
|
|
@ -400,23 +401,34 @@ class OpenBaoClient:
|
|||
self._run_ok(["lease", "revoke", lease_id])
|
||||
|
||||
def _parse_wrap_response(self, stdout: str, *, ttl: str, creation_path: str) -> WrappedResponse:
|
||||
# Verify native wrapping metadata. Never reconstruct a purported source
|
||||
# path or TTL from the caller when the backend omitted or changed it.
|
||||
try:
|
||||
payload = json.loads(stdout)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise BackendError("wrapped response is not JSON") from exc
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
raise BackendError("wrapped response is not JSON") from None
|
||||
if not isinstance(payload, dict):
|
||||
raise BackendError("wrapped response is invalid")
|
||||
wrap = payload.get("wrap_info")
|
||||
if not isinstance(wrap, dict) or not wrap.get("token"):
|
||||
raise BackendError("response wrapping was not applied")
|
||||
token = str(wrap["token"])
|
||||
accessor = str(wrap.get("accessor") or "")
|
||||
wrap_ttl = wrap.get("ttl")
|
||||
if payload.get("data") or payload.get("auth"):
|
||||
raise BackendError("wrapped response unexpectedly included unwrapped material")
|
||||
match = re.fullmatch(r"([1-9][0-9]*)([smh])", ttl)
|
||||
seconds = int(match[1]) * {"s": 1, "m": 60, "h": 3600}[match[2]] if match else 0
|
||||
if not 0 < seconds <= 900:
|
||||
raise BackendError("wrap TTL is outside the supported bound")
|
||||
token, accessor = wrap.get("token"), wrap.get("accessor")
|
||||
if (not isinstance(token, str) or not token or any(ch.isspace() for ch in token)
|
||||
or not isinstance(accessor, str) or not accessor
|
||||
or type(wrap.get("ttl")) is not int or wrap["ttl"] != seconds
|
||||
or wrap.get("creation_path") != creation_path):
|
||||
raise BackendError("native wrap identity, TTL or creation path unverified")
|
||||
return WrappedResponse(
|
||||
wrap_token=token,
|
||||
accessor_fingerprint=accessor_fingerprint(accessor) if accessor else "",
|
||||
ttl=str(wrap_ttl if wrap_ttl is not None else ttl),
|
||||
creation_path=creation_path,
|
||||
accessor_fingerprint=accessor_fingerprint(accessor),
|
||||
ttl=str(seconds),
|
||||
creation_path=wrap["creation_path"],
|
||||
)
|
||||
|
||||
def wrap_kv_get(self, mount: str, path: str, *, ttl: str) -> WrappedResponse:
|
||||
|
|
@ -425,7 +437,7 @@ class OpenBaoClient:
|
|||
proc = self._run(["kv", "get", f"-wrap-ttl={ttl}", "-format=json", target])
|
||||
if proc.returncode != 0:
|
||||
raise BackendError("wrapped KV read failed")
|
||||
return self._parse_wrap_response(proc.stdout, ttl=ttl, creation_path=target)
|
||||
return self._parse_wrap_response(proc.stdout, ttl=ttl, creation_path=f"{mount}/data/{path}")
|
||||
|
||||
def wrap_approle_secret_id(self, role_name: str, *, ttl: str) -> WrappedResponse:
|
||||
"""Wrap a single-use AppRole secret_id. Fail if returned unwrapped."""
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -43,33 +44,48 @@ def normalize_wrap_ttl(value: str) -> str:
|
|||
|
||||
|
||||
def _validate_output_path(path: Path) -> Path:
|
||||
resolved = path.expanduser().resolve()
|
||||
worktree = containing_git_worktree(resolved)
|
||||
# Do not follow a leaf/parent symlink into another recipient's private file.
|
||||
absolute = Path(os.path.abspath(path.expanduser()))
|
||||
if absolute.resolve() != absolute or absolute.is_symlink():
|
||||
raise ProvisioningError("wrap output cannot use redirected paths")
|
||||
worktree = containing_git_worktree(absolute)
|
||||
if worktree is not None:
|
||||
raise ProvisioningError(
|
||||
f"wrap file {resolved} is inside a Git worktree ({worktree}); "
|
||||
"keep wrap tokens outside repos"
|
||||
)
|
||||
if resolved.exists() and resolved.stat().st_mode & 0o077:
|
||||
raise ProvisioningError(
|
||||
f"wrap file {resolved} is group/other-accessible "
|
||||
f"(mode {oct(resolved.stat().st_mode & 0o777)}); must be 0600"
|
||||
)
|
||||
return resolved
|
||||
raise ProvisioningError("wrap output is inside a Git worktree; keep tokens outside repos")
|
||||
if os.path.lexists(absolute):
|
||||
raise ProvisioningError("wrap output already exists; reconcile the prior attempt, never overwrite or remint")
|
||||
return absolute
|
||||
|
||||
|
||||
def _write_mode_0600(path: Path, value: str) -> None:
|
||||
def _reserve_mode_0600(path: Path) -> int:
|
||||
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
fd: int | None = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
directory_fd = os.open("/", os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.fchmod(fd, 0o600)
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
fd = None
|
||||
fh.write(value)
|
||||
fh.write("\n")
|
||||
# Walk using directory descriptors so a concurrent symlink swap cannot
|
||||
# redirect either an ancestor or the final file creation.
|
||||
for component in path.parent.parts[1:]:
|
||||
next_fd = os.open(component, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
|
||||
dir_fd=directory_fd)
|
||||
os.close(directory_fd)
|
||||
directory_fd = next_fd
|
||||
parent = os.fstat(directory_fd)
|
||||
if parent.st_uid != os.geteuid() or stat.S_IMODE(parent.st_mode) & 0o077:
|
||||
raise ProvisioningError("wrap output parent must be owned and private")
|
||||
# Reserve before the backend call. Failure/crash leaves this private
|
||||
# marker for reconciliation, never automatic remint.
|
||||
file_fd = os.open(path.name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
|
||||
0o600, dir_fd=directory_fd)
|
||||
try:
|
||||
os.fchmod(file_fd, 0o600)
|
||||
os.fsync(file_fd)
|
||||
os.fsync(directory_fd)
|
||||
except BaseException:
|
||||
os.close(file_fd)
|
||||
raise
|
||||
return file_fd
|
||||
except OSError:
|
||||
raise ProvisioningError("wrap output reservation failed; no backend retry") from None
|
||||
finally:
|
||||
if fd is not None:
|
||||
os.close(fd)
|
||||
os.close(directory_fd)
|
||||
|
||||
|
||||
def write_wrapped_handoff(
|
||||
|
|
@ -82,20 +98,26 @@ def write_wrapped_handoff(
|
|||
"""Mint a wrap token and write it without printing it."""
|
||||
ttl = normalize_wrap_ttl(ttl)
|
||||
out_path = _validate_output_path(out_file)
|
||||
wrapped: WrappedResponse
|
||||
if entry.kind == "auth-capability":
|
||||
wrapped = client.wrap_approle_secret_id(entry.role_name, ttl=ttl)
|
||||
elif entry.stores_kv_value():
|
||||
wrapped = client.wrap_kv_get(entry.mount, entry.path, ttl=ttl)
|
||||
else:
|
||||
raise ProvisioningError(
|
||||
f"lane '{entry.id}' kind {entry.kind} has no wrapped handoff"
|
||||
)
|
||||
token = wrapped.wrap_token
|
||||
if entry.kind != "auth-capability" and not entry.stores_kv_value():
|
||||
raise ProvisioningError(f"lane '{entry.id}' kind {entry.kind} has no wrapped handoff")
|
||||
fd = _reserve_mode_0600(out_path)
|
||||
try:
|
||||
_write_mode_0600(out_path, token)
|
||||
if entry.kind == "auth-capability":
|
||||
wrapped: WrappedResponse = client.wrap_approle_secret_id(entry.role_name, ttl=ttl)
|
||||
else:
|
||||
wrapped = client.wrap_kv_get(entry.mount, entry.path, ttl=ttl)
|
||||
visible, reserved = out_path.lstat(), os.fstat(fd)
|
||||
if (not stat.S_ISREG(visible.st_mode)
|
||||
or (visible.st_dev, visible.st_ino) != (reserved.st_dev, reserved.st_ino)):
|
||||
raise ProvisioningError("wrap output reservation changed; retain and reconcile the mint")
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
||||
fd = None
|
||||
handle.write(wrapped.wrap_token + "\n")
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
finally:
|
||||
token = ""
|
||||
if fd is not None:
|
||||
os.close(fd)
|
||||
return WrapHandoffResult(
|
||||
catalog_id=entry.id,
|
||||
kind=entry.kind,
|
||||
|
|
|
|||
|
|
@ -65,13 +65,17 @@ def test_wrap_kv_writes_0600_file_and_omits_token_from_result(tmp_path):
|
|||
assert WRAP_ACCESSOR not in result.wrap_handle
|
||||
|
||||
|
||||
def test_wrap_rejects_repo_paths():
|
||||
def test_wrap_rejects_repo_paths(tmp_path):
|
||||
import subprocess
|
||||
repository = tmp_path / "repository"
|
||||
repository.mkdir()
|
||||
subprocess.run(["git", "init", str(repository)], check=True, capture_output=True)
|
||||
entry = validate_entry(copy.deepcopy(VALID))
|
||||
with pytest.raises(ProvisioningError, match="Git worktree"):
|
||||
write_wrapped_handoff(
|
||||
FakeWrapClient(),
|
||||
entry,
|
||||
out_file=__import__("pathlib").Path("wrap.token"),
|
||||
out_file=repository / "wrap.token",
|
||||
ttl="15m",
|
||||
)
|
||||
|
||||
|
|
@ -96,6 +100,7 @@ def test_parse_wrap_accepts_wrap_info_only():
|
|||
"token": WRAP_TOKEN,
|
||||
"accessor": WRAP_ACCESSOR,
|
||||
"ttl": 900,
|
||||
"creation_path": "secret/x",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -116,7 +121,7 @@ def test_wrap_kv_get_uses_wrap_ttl_flag(monkeypatch):
|
|||
return SimpleNamespace(
|
||||
returncode=0,
|
||||
stdout=json.dumps(
|
||||
{"wrap_info": {"token": WRAP_TOKEN, "accessor": WRAP_ACCESSOR}}
|
||||
{"wrap_info": {"token": WRAP_TOKEN, "accessor": WRAP_ACCESSOR, "ttl": 900, "creation_path": "secret/data/test/team/thing"}}
|
||||
),
|
||||
stderr="",
|
||||
)
|
||||
|
|
@ -158,3 +163,93 @@ def test_production_wrap_fails_closed(tmp_path, monkeypatch):
|
|||
)
|
||||
with pytest.raises(DecisionError, match="production action 'wrap'"):
|
||||
cli.cmd_wrap(cfg, args)
|
||||
|
||||
@pytest.mark.parametrize('change', [
|
||||
{'creation_path':'secret/data/other-recipient'}, {'creation_path':None},
|
||||
{'ttl':901}, {'ttl':60}, {'ttl':True}, {'ttl':None},
|
||||
{'token':42}, {'token':'value with whitespace'}, {'accessor':None},
|
||||
])
|
||||
def test_native_wrap_binding_cannot_be_inferred_from_request(change):
|
||||
client=OpenBaoClient(addr='http://example.invalid',token='t',bao_bin='bao')
|
||||
value={'token':WRAP_TOKEN,'accessor':WRAP_ACCESSOR,'ttl':900,'creation_path':'secret/data/test/team/thing'}
|
||||
value.update(change)
|
||||
with pytest.raises(BackendError) as raised:
|
||||
client._parse_wrap_response(json.dumps({'wrap_info':value}),ttl='15m',creation_path='secret/data/test/team/thing')
|
||||
assert WRAP_TOKEN not in str(raised.value)
|
||||
|
||||
|
||||
def test_valid_wrap_info_cannot_mask_unwrapped_payload():
|
||||
client=OpenBaoClient(addr='http://example.invalid',token='t',bao_bin='bao')
|
||||
value={'wrap_info':{'token':WRAP_TOKEN,'accessor':WRAP_ACCESSOR,'ttl':900,'creation_path':'secret/x'},'data':{'secret':'NEVER-LOG-ME'}}
|
||||
with pytest.raises(BackendError) as raised:
|
||||
client._parse_wrap_response(json.dumps(value),ttl='15m',creation_path='secret/x')
|
||||
assert 'NEVER-LOG-ME' not in str(raised.value)
|
||||
|
||||
|
||||
def test_existing_output_prevents_mint_and_preserves_original(tmp_path):
|
||||
out=tmp_path/'wrap.token';out.write_text('previous trial');out.chmod(0o600)
|
||||
client=FakeWrapClient()
|
||||
with pytest.raises(ProvisioningError,match='already exists'):
|
||||
write_wrapped_handoff(client,validate_entry(copy.deepcopy(VALID)),out_file=out,ttl='60s')
|
||||
assert client.calls==[] and out.read_text()=='previous trial'
|
||||
|
||||
|
||||
def test_leaf_and_parent_symlink_cannot_redirect_handoff(tmp_path):
|
||||
from pathlib import Path
|
||||
target=tmp_path/'retained';target.write_text('private existing');target.chmod(0o600)
|
||||
out=tmp_path/'wrap.token';out.symlink_to(target)
|
||||
client=FakeWrapClient();entry=validate_entry(copy.deepcopy(VALID))
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
folder=tmp_path/'private';folder.mkdir(mode=0o700)
|
||||
parent=tmp_path/'redirect';parent.symlink_to(folder,target_is_directory=True)
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=parent/'wrap.token',ttl='60s')
|
||||
assert client.calls==[] and target.read_text()=='private existing'
|
||||
|
||||
|
||||
def test_backend_failure_retains_private_reservation_and_prevents_remint(tmp_path):
|
||||
class Failed(FakeWrapClient):
|
||||
def wrap_kv_get(self,*args,**kwargs):
|
||||
self.calls.append('attempt');raise BackendError('unverified native mint')
|
||||
client=Failed();out=tmp_path/'wrap.token';entry=validate_entry(copy.deepcopy(VALID))
|
||||
with pytest.raises(BackendError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
assert out.exists() and out.read_bytes()==b'' and out.stat().st_mode&0o077==0
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
assert client.calls==['attempt']
|
||||
|
||||
|
||||
def test_shared_output_directory_refuses_before_backend(tmp_path):
|
||||
parent=tmp_path/'shared';parent.mkdir(mode=0o755);client=FakeWrapClient()
|
||||
with pytest.raises(ProvisioningError,match='owned and private'):
|
||||
write_wrapped_handoff(client,validate_entry(copy.deepcopy(VALID)),out_file=parent/'wrap.token',ttl='60s')
|
||||
assert client.calls==[]
|
||||
|
||||
|
||||
def test_concurrent_handoff_reserves_before_only_one_backend_mint(tmp_path):
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import threading
|
||||
entered,release=threading.Event(),threading.Event()
|
||||
class Paused(FakeWrapClient):
|
||||
def wrap_kv_get(self,*args,**kwargs):
|
||||
entered.set();assert release.wait(5)
|
||||
return super().wrap_kv_get(*args,**kwargs)
|
||||
client=Paused();entry=validate_entry(copy.deepcopy(VALID));out=tmp_path/'wrap.token'
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
first=pool.submit(write_wrapped_handoff,client,entry,out_file=out,ttl='60s')
|
||||
assert entered.wait(5)
|
||||
try:
|
||||
with pytest.raises(ProvisioningError):write_wrapped_handoff(client,entry,out_file=out,ttl='60s')
|
||||
finally:release.set()
|
||||
first.result()
|
||||
assert len(client.calls)==1 and out.read_text().strip()==WRAP_TOKEN
|
||||
|
||||
|
||||
def test_output_replacement_during_backend_call_does_not_overwrite_other_recipient(tmp_path):
|
||||
out=tmp_path/'wrap.token';other=tmp_path/'other';other.write_text('retained recipient')
|
||||
class Replaced(FakeWrapClient):
|
||||
def wrap_kv_get(self,*args,**kwargs):
|
||||
out.unlink();out.symlink_to(other)
|
||||
return super().wrap_kv_get(*args,**kwargs)
|
||||
client=Replaced()
|
||||
with pytest.raises(ProvisioningError,match='reservation changed'):
|
||||
write_wrapped_handoff(client,validate_entry(copy.deepcopy(VALID)),out_file=out,ttl='60s')
|
||||
assert other.read_text()=='retained recipient' and len(client.calls)==1
|
||||
|
|
|
|||
24
tests/test_wrapped_issuer_native.py
Normal file
24
tests/test_wrapped_issuer_native.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""Real wrapping semantics in a disposable local OpenBao, not estate authority."""
|
||||
import json
|
||||
import re
|
||||
|
||||
from secrets_engine.openbao import OpenBaoClient
|
||||
from tests.test_integration_bao import bao_dev, pytestmark # noqa: F401
|
||||
|
||||
|
||||
def test_native_wrap_path_ttl_and_one_time_unwrap(bao_dev):
|
||||
written=bao_dev._run(['kv','put','-format=json','secret/d3-wrap-fixture',
|
||||
'marker=synthetic-wrapped-integration-value'])
|
||||
assert written.returncode==0
|
||||
wrapped=bao_dev.wrap_kv_get('secret','d3-wrap-fixture',ttl='60s')
|
||||
assert wrapped.creation_path=='secret/data/d3-wrap-fixture'
|
||||
assert wrapped.ttl=='60'
|
||||
assert wrapped.wrap_token not in repr(wrapped) and wrapped.wrap_token not in json.dumps(wrapped.evidence())
|
||||
recipient=OpenBaoClient(addr=bao_dev.addr,token=wrapped.wrap_token,bao_bin=bao_dev.bao_bin)
|
||||
first=recipient._run(['unwrap','-format=json'])
|
||||
assert first.returncode==0
|
||||
assert json.loads(first.stdout)['data']['data']['marker']=='synthetic-wrapped-integration-value'
|
||||
replay=recipient._run(['unwrap','-format=json'])
|
||||
assert replay.returncode==2 and not replay.stdout.strip()
|
||||
assert re.search(r'Code:\s*400\b',replay.stderr)
|
||||
assert '* wrapping token is not valid or does not exist' in replay.stderr
|
||||
|
|
@ -24,7 +24,10 @@ unchanged identity-only login and is not a protected execution entitlement.
|
|||
|
||||
```task
|
||||
id: SECRETS-WP-0013-T01
|
||||
status: progress
|
||||
status: wait
|
||||
needs_human: false
|
||||
blocked_on: message-from:railiance-platform
|
||||
blocking_reason: "Independent Platform mediated-contract review and identity/approval/PDP owner returns remain required; D3 wrapped primitive source is implemented, no native entitlement is inferred."
|
||||
priority: high
|
||||
state_hub_task_id: "b2f0d462-a542-518b-a26b-b7ec07b3c7c1"
|
||||
```
|
||||
|
|
@ -45,6 +48,8 @@ in the execution-contract document. No source owner assent, initiating-principal
|
|||
admission, new policy package, ledger/mediator/backend or native acceptance is
|
||||
claimed. Independent source review remains required. Task statuses are unchanged.
|
||||
|
||||
2026-10-11: D3 response-wrapped primitive now verifies native creation path and bounded TTL/accessor, rejects overwrite/symlink/shared-parent paths, and reserves/fsyncs the private recipient before mint. Backend/write uncertainty retains a non-replayable reservation. Disposable OpenBao proves one successful unwrap and native consumed-token HTTP400 denial, not a production lane or entitlement. Separate delivery contracts cannot reuse metadata execute-ccr authority. Source design is ready for independent owner review; RPF-IN-0012 requests the Platform return. Identity/approval/PDP returns and the complete native mediated path remain open; no founder decision or foreign owner assent is fabricated.
|
||||
|
||||
## Prevent consumption without execution readiness
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue