From d6fae7be3c30b254d4905fb3595a5594077cf286 Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Mon, 13 Jul 2026 02:06:59 +0200 Subject: [PATCH] add private macOS password storage skill --- .../skills/private-password-storage/SKILL.md | 63 ++++ .../agents/openai.yaml | 4 + .../scripts/private-password-storage | 5 + .../scripts/private_password_storage.py | 191 ++++++++++ .../scripts/private_password_storage.swift | 346 ++++++++++++++++++ tests/test_private_password_storage_skill.py | 178 +++++++++ 6 files changed, 787 insertions(+) create mode 100644 .agents/skills/private-password-storage/SKILL.md create mode 100644 .agents/skills/private-password-storage/agents/openai.yaml create mode 100755 .agents/skills/private-password-storage/scripts/private-password-storage create mode 100755 .agents/skills/private-password-storage/scripts/private_password_storage.py create mode 100644 .agents/skills/private-password-storage/scripts/private_password_storage.swift create mode 100644 tests/test_private_password_storage_skill.py diff --git a/.agents/skills/private-password-storage/SKILL.md b/.agents/skills/private-password-storage/SKILL.md new file mode 100644 index 0000000..f02ead6 --- /dev/null +++ b/.agents/skills/private-password-storage/SKILL.md @@ -0,0 +1,63 @@ +--- +name: private-password-storage +description: Store or update a private password or credential in the device-local macOS Keychain through a native secure popup, and check presence without retrieval. Use when a user asks to save, store, update, or verify a private credential securely on a Mac. +--- + +# Private Password Storage + +Use the bundled helper so the credential enters only through a native +`NSSecureTextField` and goes directly to Security.framework. + +## Choose The Right Authentication Route + +Prefer the provider's supported credential manager or login flow when one +exists. Keychain storage does not authenticate an application and cannot +satisfy OAuth, OTP, passkeys, browser sessions, or `gcloud` login by itself. + +Never ask the user to paste a credential into chat. + +## Store Or Update + +Run `--store` only after the user asks to open the secure popup: + +```bash +.agents/skills/private-password-storage/scripts/private-password-storage \ + --service "com.example.application" \ + --account "operator@example.invalid" \ + --label "Example application password" \ + --store +``` + +The popup accepts normal typing, Cmd+V, and right-click Paste. Empty input +stays in the popup and is rejected. A successful write prints only `stored`. +The item is stored as `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` with +synchronization disabled. + +## Check Presence + +```bash +.agents/skills/private-password-storage/scripts/private-password-storage \ + --service "com.example.application" \ + --account "operator@example.invalid" \ + --status +``` + +Status prints only `present` or `absent`. It never returns the credential. + +## Safety Contract + +- Keep the credential out of process arguments, shell variables, files, + logs, screenshots, terminal output, clipboard writes, and proof artifacts. +- Do not add retrieval, reveal, echo, export, or fingerprint modes. +- Do not use AppleScript, `osascript`, System Events, `pbcopy`, or the + `security add-generic-password` CLI. +- Treat service, account, and label as non-secret metadata; do not print them. +- Report only the helper status and whether a native popup was used. + +## Sanitized Canary + +Use only a generated fake value and a service beginning with +`dev.codex.private-password-storage.canary.` plus an account beginning with +`canary-`. The test-only `--delete-test-item` action is prefix-guarded and +deletes only that exact service/account pair. Verify `present`, delete the +exact item, then verify `absent`. Do not retain the fake value anywhere. diff --git a/.agents/skills/private-password-storage/agents/openai.yaml b/.agents/skills/private-password-storage/agents/openai.yaml new file mode 100644 index 0000000..30f5762 --- /dev/null +++ b/.agents/skills/private-password-storage/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Private Password Storage" + short_description: "Store device-local credentials securely" + default_prompt: "Use $private-password-storage to store or update a private credential securely in the macOS Keychain." diff --git a/.agents/skills/private-password-storage/scripts/private-password-storage b/.agents/skills/private-password-storage/scripts/private-password-storage new file mode 100755 index 0000000..4eba6b3 --- /dev/null +++ b/.agents/skills/private-password-storage/scripts/private-password-storage @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec python3 "$SCRIPT_DIR/private_password_storage.py" "$@" diff --git a/.agents/skills/private-password-storage/scripts/private_password_storage.py b/.agents/skills/private-password-storage/scripts/private_password_storage.py new file mode 100755 index 0000000..f069102 --- /dev/null +++ b/.agents/skills/private-password-storage/scripts/private_password_storage.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Launch the native macOS Keychain helper without accepting secret arguments.""" + +from __future__ import annotations + +import argparse +import os +import platform +import plistlib +import subprocess +import sys +import tempfile +from collections.abc import Iterator +from contextlib import contextmanager +from pathlib import Path + + +class SafeArgumentParser(argparse.ArgumentParser): + def error(self, message: str) -> None: + raise ValueError(message) + + +def parse_args() -> argparse.Namespace: + parser = SafeArgumentParser(allow_abbrev=False) + parser.add_argument("--service", required=True) + parser.add_argument("--account", required=True) + parser.add_argument("--label", default="Private credential") + actions = parser.add_mutually_exclusive_group(required=True) + actions.add_argument("--store", action="store_true") + actions.add_argument("--status", action="store_true") + actions.add_argument( + "--delete-test-item", action="store_true", help=argparse.SUPPRESS + ) + return parser.parse_args() + + +def validate_metadata(args: argparse.Namespace) -> bool: + values = (args.service, args.account, args.label) + return all(value and "\x00" not in value for value in values) + + +@contextmanager +def native_command() -> Iterator[tuple[list[str], Path | None] | None]: + test_helper = os.environ.get("PRIVATE_PASSWORD_STORAGE_TEST_HELPER") + if test_helper: + yield ([test_helper], None) + return + if sys.platform != "darwin": + yield None + return + + discovery = subprocess.run( + ["/usr/bin/xcrun", "--find", "swiftc"], + check=False, + capture_output=True, + text=True, + ) + if discovery.returncode != 0: + yield None + return + if not discovery.stdout.strip(): + yield None + return + + target = f"{platform.machine()}-apple-macosx12.0" + with tempfile.TemporaryDirectory(prefix="private-password-storage-") as temp: + app = Path(temp) / "Private Password Storage.app" + macos = app / "Contents" / "MacOS" + macos.mkdir(parents=True) + executable = macos / "private-password-storage-native" + info = { + "CFBundleDisplayName": "Private Password Storage", + "CFBundleExecutable": executable.name, + "CFBundleIdentifier": "dev.codex.private-password-storage", + "CFBundleName": "Private Password Storage", + "CFBundlePackageType": "APPL", + "CFBundleVersion": "1", + "CFBundleShortVersionString": "1.0", + "LSMinimumSystemVersion": "12.0", + "NSHighResolutionCapable": True, + } + with (app / "Contents" / "Info.plist").open("wb") as handle: + plistlib.dump(info, handle) + + compilation = subprocess.run( + [ + "/usr/bin/xcrun", + "swiftc", + "-target", + target, + str(Path(__file__).with_suffix(".swift")), + "-o", + str(executable), + ], + check=False, + capture_output=True, + text=True, + ) + if compilation.returncode != 0: + yield None + return + yield ([str(executable)], app) + + +def action_args(args: argparse.Namespace) -> list[str]: + if args.store: + action = "--store" + elif args.status: + action = "--status" + else: + action = "--delete-test-item" + return [ + "--service", + args.service, + "--account", + args.account, + "--label", + args.label, + action, + ] + + +def main() -> int: + try: + args = parse_args() + except (ValueError, SystemExit): + sys.stdout.write("error\n") + return 64 + + if not validate_metadata(args): + sys.stdout.write("error\n") + return 64 + + with native_command() as native: + if native is None: + sys.stdout.write("error\n") + return 69 + command, app = native + try: + if args.store and app is not None: + result_file = app.parent / "store-status" + completed = subprocess.run( + [ + "/usr/bin/open", + "-W", + "-n", + str(app), + "--args", + *action_args(args), + "--result-file", + str(result_file), + ], + check=False, + capture_output=True, + text=True, + ) + output = ( + result_file.read_text(encoding="utf-8").strip() + if result_file.is_file() + else "" + ) + else: + completed = subprocess.run( + [*command, *action_args(args)], + check=False, + capture_output=True, + text=True, + ) + output = completed.stdout.strip() + except KeyboardInterrupt: + sys.stdout.write("cancelled\n") + return 1 + + allowed = { + "stored": {"stored", "cancelled"}, + "status": {"present", "absent"}, + "delete": {"absent"}, + } + expected = "stored" if args.store else "status" if args.status else "delete" + if completed.returncode not in (0, 1) or output not in allowed[expected]: + sys.stdout.write("error\n") + return 1 + + sys.stdout.write(f"{output}\n") + if expected == "stored" and output != "stored": + return 1 + return completed.returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.agents/skills/private-password-storage/scripts/private_password_storage.swift b/.agents/skills/private-password-storage/scripts/private_password_storage.swift new file mode 100644 index 0000000..3e93c53 --- /dev/null +++ b/.agents/skills/private-password-storage/scripts/private_password_storage.swift @@ -0,0 +1,346 @@ +import AppKit +import Foundation +import Security + +private enum Action { + case store + case status + case deleteTestItem +} + +private struct Options { + let service: String + let account: String + let label: String + let action: Action + let resultFile: String? +} + +private enum StoreResult { + case stored + case cancelled + case failed +} + +private enum PresenceResult { + case present + case absent + case failed +} + +private func parseOptions() -> Options? { + var service: String? + var account: String? + var label = "Private credential" + var action: Action? + var resultFile: String? + var index = 1 + let arguments = CommandLine.arguments + + while index < arguments.count { + let argument = arguments[index] + switch argument { + case "--service", "--account", "--label", "--result-file": + guard index + 1 < arguments.count else { return nil } + let value = arguments[index + 1] + guard !value.isEmpty, !value.contains("\0") else { return nil } + if argument == "--service" { + service = value + } else if argument == "--account" { + account = value + } else if argument == "--label" { + label = value + } else { + resultFile = value + } + index += 2 + case "--store", "--status", "--delete-test-item": + guard action == nil else { return nil } + action = argument == "--store" ? .store + : argument == "--status" ? .status : .deleteTestItem + index += 1 + default: + return nil + } + } + + guard let service, let account, let action, !label.isEmpty else { return nil } + return Options( + service: service, + account: account, + label: label, + action: action, + resultFile: resultFile + ) +} + +private func deviceLocalQuery(service: String, account: String) -> [CFString: Any] { + [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: service, + kSecAttrAccount: account, + kSecAttrSynchronizable: kCFBooleanFalse as Any, + ] +} + +private func storeInKeychain( + service: String, + account: String, + label: String, + secretData: Data +) -> Bool { + let query = deviceLocalQuery(service: service, account: account) + var item = query + item[kSecAttrLabel] = label + item[kSecAttrAccessible] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly + item[kSecValueData] = secretData + + let addStatus = SecItemAdd(item as CFDictionary, nil) + if addStatus == errSecSuccess { + return true + } + guard addStatus == errSecDuplicateItem else { + return false + } + + let updates: [CFString: Any] = [ + kSecAttrLabel: label, + kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + kSecValueData: secretData, + ] + return SecItemUpdate(query as CFDictionary, updates as CFDictionary) == errSecSuccess +} + +private func keychainPresence(service: String, account: String) -> PresenceResult { + var query = deviceLocalQuery(service: service, account: account) + query[kSecMatchLimit] = kSecMatchLimitOne + query[kSecReturnData] = kCFBooleanFalse + let status = SecItemCopyMatching(query as CFDictionary, nil) + if status == errSecSuccess { + return .present + } + if status == errSecItemNotFound { + return .absent + } + return .failed +} + +private func deleteExactCanaryItem(service: String, account: String) -> Bool { + guard service.hasPrefix("dev.codex.private-password-storage.canary."), + account.hasPrefix("canary-") else { + return false + } + let query = deviceLocalQuery(service: service, account: account) + let status = SecItemDelete(query as CFDictionary) + return status == errSecSuccess || status == errSecItemNotFound +} + +@MainActor +private final class SecurePromptController: NSObject, NSWindowDelegate { + private let service: String + private let account: String + private let itemLabel: String + private let window: NSWindow + private let secureField = NSSecureTextField() + private let validationLabel = NSTextField(labelWithString: "") + private var result: StoreResult? + + init(service: String, account: String, label: String) { + self.service = service + self.account = account + self.itemLabel = label + self.window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 440, height: 190), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + super.init() + configureWindow() + } + + private func configureWindow() { + window.title = "Store Private Credential" + window.isReleasedWhenClosed = false + window.delegate = self + window.center() + + let content = NSView() + window.contentView = content + + let title = NSTextField(labelWithString: itemLabel) + title.font = NSFont.systemFont(ofSize: 15, weight: .semibold) + title.lineBreakMode = .byTruncatingTail + secureField.placeholderString = "Password" + secureField.font = NSFont.systemFont(ofSize: 14) + + validationLabel.stringValue = "Password cannot be empty." + validationLabel.textColor = .systemRed + validationLabel.isHidden = true + + let cancelButton = NSButton( + title: "Cancel", target: self, action: #selector(cancelPressed) + ) + cancelButton.keyEquivalent = "\u{1b}" + let storeButton = NSButton( + title: "Store", target: self, action: #selector(storePressed) + ) + storeButton.keyEquivalent = "\r" + + let pasteMenu = NSMenu() + let pasteItem = NSMenuItem( + title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "" + ) + pasteItem.target = nil + pasteMenu.addItem(pasteItem) + secureField.menu = pasteMenu + + [title, secureField, validationLabel, cancelButton, storeButton].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + content.addSubview($0) + } + + NSLayoutConstraint.activate([ + title.topAnchor.constraint(equalTo: content.topAnchor, constant: 24), + title.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 24), + title.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -24), + secureField.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16), + secureField.leadingAnchor.constraint(equalTo: title.leadingAnchor), + secureField.trailingAnchor.constraint(equalTo: title.trailingAnchor), + secureField.heightAnchor.constraint(equalToConstant: 28), + validationLabel.topAnchor.constraint(equalTo: secureField.bottomAnchor, constant: 6), + validationLabel.leadingAnchor.constraint(equalTo: secureField.leadingAnchor), + storeButton.trailingAnchor.constraint(equalTo: secureField.trailingAnchor), + storeButton.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -20), + cancelButton.trailingAnchor.constraint(equalTo: storeButton.leadingAnchor, constant: -10), + cancelButton.centerYAnchor.constraint(equalTo: storeButton.centerYAnchor), + ]) + } + + private func installPasteMenu() { + let mainMenu = NSMenu() + let editMenuItem = NSMenuItem() + let editMenu = NSMenu(title: "Edit") + let pasteItem = NSMenuItem( + title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v" + ) + pasteItem.keyEquivalentModifierMask = [.command] + pasteItem.target = nil + editMenu.addItem(pasteItem) + editMenuItem.submenu = editMenu + mainMenu.addItem(editMenuItem) + NSApp.mainMenu = mainMenu + } + + func run() -> StoreResult { + installPasteMenu() + NSApp.setActivationPolicy(.regular) + NSApp.activate(ignoringOtherApps: true) + window.makeKeyAndOrderFront(nil) + window.makeFirstResponder(secureField) + NSApp.run() + return result ?? .cancelled + } + + @objc private func storePressed() { + guard !secureField.stringValue.isEmpty else { + validationLabel.stringValue = "Password cannot be empty." + validationLabel.isHidden = false + NSSound.beep() + window.makeFirstResponder(secureField) + return + } + + validationLabel.isHidden = true + var secretData = Data(secureField.stringValue.utf8) + let stored = storeInKeychain( + service: service, + account: account, + label: itemLabel, + secretData: secretData + ) + secureField.stringValue = "" + secretData.resetBytes(in: 0.. StoreResult { + let application = NSApplication.shared + let controller = SecurePromptController( + service: options.service, + account: options.account, + label: options.label + ) + _ = application + return controller.run() +} + +private func emit(_ value: String, code: Int32, resultFile: String? = nil) -> Never { + let data = Data((value + "\n").utf8) + if let resultFile { + do { + try data.write(to: URL(fileURLWithPath: resultFile), options: .atomic) + } catch { + exit(74) + } + } else { + FileHandle.standardOutput.write(data) + } + exit(code) +} + +guard let options = parseOptions() else { + emit("error", code: 64) +} + +switch options.action { +case .status: + switch keychainPresence(service: options.service, account: options.account) { + case .present: + emit("present", code: 0, resultFile: options.resultFile) + case .absent: + emit("absent", code: 1, resultFile: options.resultFile) + case .failed: + emit("error", code: 1, resultFile: options.resultFile) + } +case .store: + let storeResult = MainActor.assumeIsolated { + runSecurePrompt(options: options) + } + switch storeResult { + case .stored: + emit("stored", code: 0, resultFile: options.resultFile) + case .cancelled: + emit("cancelled", code: 1, resultFile: options.resultFile) + case .failed: + emit("error", code: 1, resultFile: options.resultFile) + } +case .deleteTestItem: + guard deleteExactCanaryItem(service: options.service, account: options.account) else { + emit("error", code: 1, resultFile: options.resultFile) + } + emit("absent", code: 0, resultFile: options.resultFile) +} diff --git a/tests/test_private_password_storage_skill.py b/tests/test_private_password_storage_skill.py new file mode 100644 index 0000000..2c2bd7f --- /dev/null +++ b/tests/test_private_password_storage_skill.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import json +import os +import platform +import subprocess +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +SKILL = ROOT / ".agents" / "skills" / "private-password-storage" +LAUNCHER = SKILL / "scripts" / "private-password-storage" +PYTHON_HELPER = SKILL / "scripts" / "private_password_storage.py" +SWIFT_HELPER = SKILL / "scripts" / "private_password_storage.swift" + + +def _fake_helper(tmp_path: Path) -> tuple[Path, Path]: + helper = tmp_path / "fake_keychain_helper.py" + capture = tmp_path / "captured_args.json" + helper.write_text( + """#!/usr/bin/env python3 +import json +import os +import sys + +with open(os.environ["PRIVATE_PASSWORD_STORAGE_TEST_CAPTURE"], "w", encoding="utf-8") as handle: + json.dump(sys.argv[1:], handle) + +if "--store" in sys.argv: + print("stored") +elif "--status" in sys.argv: + print("present") +elif "--delete-test-item" in sys.argv: + print("absent") +else: + print("error") + raise SystemExit(64) +""", + encoding="utf-8", + ) + helper.chmod(0o700) + return helper, capture + + +def _run_fake(tmp_path: Path, action: str) -> tuple[subprocess.CompletedProcess[str], list[str]]: + helper, capture = _fake_helper(tmp_path) + env = os.environ.copy() + env["PRIVATE_PASSWORD_STORAGE_TEST_HELPER"] = str(helper) + env["PRIVATE_PASSWORD_STORAGE_TEST_CAPTURE"] = str(capture) + completed = subprocess.run( + [ + str(LAUNCHER), + "--service", + "dev.codex.private-password-storage.canary.ci", + "--account", + "canary-ci", + "--label", + "CI fake credential", + action, + ], + check=False, + capture_output=True, + text=True, + env=env, + ) + return completed, json.loads(capture.read_text(encoding="utf-8")) + + +@pytest.mark.parametrize( + ("action", "expected", "returncode"), + [ + ("--store", "stored\n", 0), + ("--status", "present\n", 0), + ("--delete-test-item", "absent\n", 0), + ], +) +def test_launcher_uses_only_metadata_and_action( + tmp_path: Path, action: str, expected: str, returncode: int +) -> None: + completed, captured = _run_fake(tmp_path, action) + + assert completed.returncode == returncode + assert completed.stdout == expected + assert completed.stderr == "" + assert captured == [ + "--service", + "dev.codex.private-password-storage.canary.ci", + "--account", + "canary-ci", + "--label", + "CI fake credential", + action, + ] + + +def test_scripts_ban_unsafe_secret_paths_and_retrieval() -> None: + implementation = "\n".join( + path.read_text(encoding="utf-8") + for path in (LAUNCHER, PYTHON_HELPER, SWIFT_HELPER) + ) + banned = ( + "AppleScript", + "osascript", + "System Events", + "NSPasteboard", + "pbcopy", + "security add-generic-password", + "--get", + "--read-secret", + "--retrieve", + "kSecReturnData] = kCFBooleanTrue", + "kSecReturnAttributes", + "print(secret", + "print(password", + ) + for token in banned: + assert token not in implementation + + +def test_swift_enforces_device_local_keychain_and_secure_popup() -> None: + source = SWIFT_HELPER.read_text(encoding="utf-8") + + for required in ( + "NSSecureTextField", + 'title: "Paste"', + "keyEquivalentModifierMask = [.command]", + "guard !secureField.stringValue.isEmpty", + "kSecAttrAccessibleWhenUnlockedThisDeviceOnly", + "kSecAttrSynchronizable: kCFBooleanFalse", + "SecItemAdd", + "SecItemUpdate", + "SecItemCopyMatching", + "SecItemDelete", + 'service.hasPrefix("dev.codex.private-password-storage.canary.")', + 'account.hasPrefix("canary-")', + ): + assert required in source + + store_case = source.split("case .store:", 1)[1].split( + "case .deleteTestItem:", 1 + )[0] + assert "runSecurePrompt" in store_case + assert "runSecurePrompt" not in source.split("case .status:", 1)[1].split( + "case .store:", 1 + )[0] + + +def test_launcher_builds_an_ephemeral_identifiable_native_app() -> None: + source = PYTHON_HELPER.read_text(encoding="utf-8") + + assert 'TemporaryDirectory(prefix="private-password-storage-")' in source + assert '"CFBundleIdentifier": "dev.codex.private-password-storage"' in source + assert '"CFBundlePackageType": "APPL"' in source + assert '"NSHighResolutionCapable": True' in source + assert '"/usr/bin/open"' in source + assert 'result_file = app.parent / "store-status"' in source + assert "secretData.write" not in SWIFT_HELPER.read_text(encoding="utf-8") + + +@pytest.mark.skipif(sys.platform != "darwin", reason="requires macOS AppKit SDK") +def test_embedded_swift_helper_typechecks() -> None: + target = f"{platform.machine()}-apple-macosx12.0" + completed = subprocess.run( + [ + "/usr/bin/xcrun", + "swiftc", + "-target", + target, + "-typecheck", + str(SWIFT_HELPER), + ], + check=False, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, completed.stderr