Provision the E2E memory secret into the local vault

`nap gateway vault setup` unwraps the project memory secret once (prompt
on a terminal, or the existing argument/env paths) and stores the
secret, never the passphrase, in memory-secrets.vault.json under the
existing 0600 local vault key. The CLI and MCP workers load it
automatically and MCP never prompts, per
/decisions/local-gateway-no-interactive-unlock and
/decisions/2026-09-29-memory-secret-local-vault. Older versions stay
readable; a rotated active version reports memory_secret_rotated with
the setup next action. `vault forget` revokes local access, including
for running workers. Env passphrases still work, are persisted into the
vault, and are reported as deprecated. Search warns
e2e_index_not_provisioned instead of e2e_index_locked. Local key
creation is 0600 from the start and never replaces a published key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
HumbleTurtle
2026-09-29 00:49:48 -03:00
co-authored by Claude Opus 5.5
parent 611a4571f3
commit eed1d1d3f2
7 changed files with 1495 additions and 64 deletions
+23 -2
View File
@@ -1042,8 +1042,12 @@ def print_gateway_help(topic=None):
nap gateway schedule delete [ID | --schedule ID]""")
return 0
if topic == "vault":
print("Usage: nap gateway vault [status|process|rotate-secret] [--kind memory] [--all] [--project-id ID] [--vault-passphrase TEXT]")
print(" status Show local gateway state and pending setup requests.")
print("Usage: nap gateway vault [status|setup|forget|process|rotate-secret] [--kind memory] [--all] [--project-id ID] [--vault-passphrase TEXT]")
print(" status Show local gateway state, memory secret provisioning, and pending setup requests.")
print(" setup Store the project memory secret in local encrypted storage for this worktree.")
print(" Prompts once for the project vault passphrase; the passphrase is never stored.")
print(" The CLI and MCP workers then load the secret automatically.")
print(" forget Remove the locally stored memory secret for this worktree.")
print(" process Upload opaque client-wrapped key bundle records for backend-owned HashiCorp storage.")
print(" rotate-secret Rotate the memory project secret from the gateway worker path.")
return 0
@@ -1054,6 +1058,7 @@ def print_gateway_help(topic=None):
print(" nap gateway repair Create dedicated gateway worker credentials.")
print(" nap gateway terminal help Show terminal lifecycle commands.")
print(" nap gateway schedule help Show schedule lifecycle commands.")
print(" nap gateway vault setup Store the E2E memory secret locally (prompts once).")
print(" nap gateway vault help Show vault commands.")
return 0
@@ -1308,6 +1313,10 @@ def mcp_runtime_probe(timeout_seconds=6):
"verified": False,
},
"remote_project_access": "not_tested",
"memory_secret": (local_payload or {}).get("memory_secret"),
"deprecations": [
item.get("code") for item in (local_payload or {}).get("deprecations") or [] if isinstance(item, dict)
],
}
except Exception as exc:
return {
@@ -1417,6 +1426,18 @@ def doctor_status():
else "inspect the failed_stage field and local project binding"
),
})
if probe.get("memory_secret") == "rotated":
setup.append({
"code": "memory_secret_rotated",
"message": "the project memory secret was rotated after it was stored locally",
"next": "nap gateway vault setup",
})
if "vault_passphrase_env_deprecated" in (probe.get("deprecations") or []):
setup.append({
"code": "vault_passphrase_env_deprecated",
"message": "NAPSEER_VAULT_PASSPHRASE / NAPSEER_MASTER_PASSPHRASE are deprecated for agents; store the memory secret locally instead",
"next": "nap gateway vault setup",
})
status = "repair_required" if issues else ("setup_available" if setup else "ok")
return {
"status": status,