add private macOS password storage skill
This commit is contained in:
parent
cf796fea76
commit
d6fae7be3c
6 changed files with 787 additions and 0 deletions
63
.agents/skills/private-password-storage/SKILL.md
Normal file
63
.agents/skills/private-password-storage/SKILL.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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."
|
||||
5
.agents/skills/private-password-storage/scripts/private-password-storage
Executable file
5
.agents/skills/private-password-storage/scripts/private-password-storage
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
exec python3 "$SCRIPT_DIR/private_password_storage.py" "$@"
|
||||
191
.agents/skills/private-password-storage/scripts/private_password_storage.py
Executable file
191
.agents/skills/private-password-storage/scripts/private_password_storage.py
Executable file
|
|
@ -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())
|
||||
|
|
@ -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..<secretData.count)
|
||||
guard stored else {
|
||||
validationLabel.stringValue = "The credential could not be stored."
|
||||
validationLabel.isHidden = false
|
||||
return
|
||||
}
|
||||
|
||||
result = .stored
|
||||
window.close()
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
secureField.stringValue = ""
|
||||
result = .cancelled
|
||||
window.close()
|
||||
}
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
secureField.stringValue = ""
|
||||
if result == nil {
|
||||
result = .cancelled
|
||||
}
|
||||
NSApp.stop(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func runSecurePrompt(options: Options) -> 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)
|
||||
}
|
||||
178
tests/test_private_password_storage_skill.py
Normal file
178
tests/test_private_password_storage_skill.py
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue