146 lines
4.2 KiB
Python
146 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Regression test: socket password keychain entries are scoped per debug instance."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def get_repo_root() -> Path:
|
|
result = subprocess.run(
|
|
["git", "rev-parse", "--show-toplevel"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode == 0:
|
|
return Path(result.stdout.strip())
|
|
return Path.cwd()
|
|
|
|
|
|
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
|
|
if needle not in content:
|
|
failures.append(message)
|
|
|
|
|
|
def reject(content: str, needle: str, message: str, failures: list[str]) -> None:
|
|
if needle in content:
|
|
failures.append(message)
|
|
|
|
|
|
def main() -> int:
|
|
repo_root = get_repo_root()
|
|
cli_path = repo_root / "CLI" / "cmux.swift"
|
|
settings_path = repo_root / "Sources" / "SocketControlSettings.swift"
|
|
|
|
missing = [str(path) for path in (cli_path, settings_path) if not path.exists()]
|
|
if missing:
|
|
print("FAIL: missing expected files:")
|
|
for path in missing:
|
|
print(f"- {path}")
|
|
return 1
|
|
|
|
cli = cli_path.read_text(encoding="utf-8")
|
|
settings = settings_path.read_text(encoding="utf-8")
|
|
failures: list[str] = []
|
|
|
|
require(
|
|
cli,
|
|
"static func resolve(explicit: String?, socketPath: String) -> String?",
|
|
"CLI resolver must accept socketPath to determine scoped keychain service",
|
|
failures,
|
|
)
|
|
require(
|
|
cli,
|
|
"private static func keychainServices(socketPath: String) -> [String]",
|
|
"CLI must derive keychain services from socket context",
|
|
failures,
|
|
)
|
|
require(
|
|
cli,
|
|
'return ["\\(service).\\(scope)"]',
|
|
"CLI should use only the scoped keychain service when scope is present",
|
|
failures,
|
|
)
|
|
require(
|
|
cli,
|
|
"URL(fileURLWithPath: socketPath).lastPathComponent",
|
|
"CLI scope detection should parse the socket file name",
|
|
failures,
|
|
)
|
|
require(
|
|
cli,
|
|
"kSecUseAuthenticationContext as String: authContext",
|
|
"CLI keychain lookup must fail fast without interactive keychain prompts",
|
|
failures,
|
|
)
|
|
require(
|
|
cli,
|
|
"SocketPasswordResolver.resolve(explicit: socketPasswordArg, socketPath: socketPath)",
|
|
"CLI run path must pass socketPath into password resolution",
|
|
failures,
|
|
)
|
|
|
|
require(
|
|
settings,
|
|
"private static func keychainScope(environment: [String: String]) -> String?",
|
|
"App keychain store should compute a scoped keychain namespace",
|
|
failures,
|
|
)
|
|
require(
|
|
settings,
|
|
"environment[SocketControlSettings.launchTagEnvKey]",
|
|
"App keychain scope should prioritize CMUX_TAG",
|
|
failures,
|
|
)
|
|
require(
|
|
settings,
|
|
"URL(fileURLWithPath: socketPath).lastPathComponent",
|
|
"App keychain scope should parse the socket file name",
|
|
failures,
|
|
)
|
|
require(
|
|
settings,
|
|
"private static func keychainService(environment: [String: String]) -> String",
|
|
"App keychain service should be derived from environment scope",
|
|
failures,
|
|
)
|
|
require(
|
|
settings,
|
|
'return "\\(service).\\(scope)"',
|
|
"App keychain service should append the scoped suffix",
|
|
failures,
|
|
)
|
|
require(
|
|
settings,
|
|
"kSecAttrService as String: keychainService(environment: environment)",
|
|
"App keychain queries should use mode-specific scoped service",
|
|
failures,
|
|
)
|
|
require(
|
|
settings,
|
|
"return try? loadPassword(environment: environment)",
|
|
"configuredPassword should read keychain from matching scoped service",
|
|
failures,
|
|
)
|
|
|
|
reject(
|
|
settings,
|
|
"private static var baseQuery: [String: Any]",
|
|
"Legacy global baseQuery should not remain as a static unscoped property",
|
|
failures,
|
|
)
|
|
|
|
if failures:
|
|
print("FAIL: keychain scope regression(s) detected")
|
|
for failure in failures:
|
|
print(f"- {failure}")
|
|
return 1
|
|
|
|
print("PASS: socket password keychain service is scoped by tagged debug instance")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|