Skip to main content

Macos Keychain

@webframp/macos-keychainv2026.08.21.1· 1d agoVAULTS
01README

macOS Keychain vault using the security CLI. Emits OpenTelemetry spans for get, put, and list so vault reads are visible in traces, including reads during model and workflow runs where the host emits nothing. Span attributes carry the vault name, key name, and keychain service only — never secret values, argv, or error messages.

02Release Notes

2026.08.21.1

Changed: When the security CLI exits non-zero, the thrown error now names the subcommand that failed (e.g. security find-generic-password -s swamp -a my-key -w exited with code 44: ...) instead of just the exit code and bare stderr. For the interactive put path (security -i), the error now includes the redacted command line that was sent on stdin, since argv alone (-i) never said what operation was attempted. Secret values and their hex encoding are still stripped before the error is constructed, as before — this only adds which operation and key were involved.

03Vaults1
macos-keychainconfigurable
@webframp/macos-keychainkeychain.ts

macOS Keychain vault using the security CLI

Config Fields

FieldTypeDescription
servicestringService name for keychain items (defaults to 'swamp')
04Previous Versions12
2026.08.20.1

2026.08.20.1

Upgrade note: Bumped zod from 4.3.6 to 4.4.3. No behavioral changes — dependency version alignment only.

2026.07.30.1

2026.07.30.1

Closes the two problems tracked in #275. Every behavior below was probed on real hardware (macOS 26.5.2, build 25F84) before implementation; the probe record is in the issue.

Fixed: put no longer passes the secret as a command-line argument. Process arguments are readable by any process running as the same user, so every secret written through this vault was visible to ps, EDR agents, and anything else that records command lines. The secret is now hex-encoded and fed to security -i on stdin as add-generic-password ... -X <hex>. Hex survives the interactive parser's tokenizer unchanged, so values with spaces, quotes, backslashes, newlines, and non-ASCII bytes all store byte-exact.

Fixed: get no longer returns garbage for secrets containing non-printable or non-ASCII bytes on macOS 26. There, find-generic-password -w prints lowercase hex instead of the secret when any byte falls outside printable ASCII (0x20–0x7E). Output that looks like hex is now disambiguated through -g, whose output marks the encoding explicitly (password: 0x... versus password: "..."), and decoded only when the keychain says it is hex. A secret whose value legitimately looks like hex (for example the literal string deadbeef) is returned verbatim — never decoded. Older macOS versions are unaffected: raw output of a secret with a non-printable byte can never look like hex, so the check does not engage.

Changed: the maximum secret size for put drops from roughly 1 MiB (the argv limit) to about 2 KB (the security -i 4096-byte line buffer, minus command overhead; the exact figure depends on the service and key length). Oversize writes now fail with a descriptive error before anything is executed. This is deliberate: the interactive parser splits over-long lines and can store a silently corrupted value, which is worse for a vault than a loud refusal. Reads are unaffected — existing larger secrets still round-trip (verified to 4 KiB).

Changed: get now fails loudly instead of guessing in two cases: when the keychain reports a hex-encoded value that is not valid UTF-8 (this provider returns strings, and replacement characters would be silent corruption), and when the encoding of an ambiguous read cannot be determined. A vault that returns a wrong secret is worse than one that errors.

Changed: keys and the configured service name now reject control characters (previously only NUL was rejected in keys). A newline would split the command line put writes to security -i.

Changed: on macOS 26, a secret ending in a newline now round-trips exactly. Such values take the hex path, which is byte-exact, so the long-documented trailing-newline ambiguity only remains for raw reads on older macOS versions.

Upgrade note: no schema or config changes. If you store secrets larger than ~2 KB through this vault, put will now refuse them — store large blobs elsewhere and keep the keychain for credentials. put now requires macOS 10.15 or newer: the -X flag it relies on first shipped in Catalina (Security-59306; it is documented in security(1) since then). The size cap matches MAX_LINE_LEN 4096 in Apple's SecurityTool source, so it is a property of the interactive interface, not of this extension.

2026.07.27.1

2026.07.27.1

Changed: Bump @opentelemetry/api 1.9.0 → 1.9.1

2026.07.26.2

Added: OpenTelemetry spans on get, put, and list, named Keychain get, Keychain put, and Keychain list. Attributes: vault.name, vault.secret_key, vault.service, rpc.system, rpc.service, and rpc.method. list is unsupported by this provider and its span reports ERROR, which is honest: a caller asked for a listing and did not get one.

This closes a real observability gap rather than duplicating the host. swamp emits swamp.vault.* spans when a human runs a swamp vault subcommand, with no attributes at all — and emits nothing when a model or workflow resolves a vault expression. A secret read during a run was invisible in traces. These spans appear on both paths.

The extension uses @opentelemetry/api only and never constructs a TracerProvider. With no provider configured the tracer is a no-op and the cost is a few property lookups.

There are deliberately no spans around the security invocation itself. put passes the secret as the -w argument, so keeping span code out of the exec helper means no edit can attach argv to a span. A test asserts the secret is genuinely present in argv and absent from every span field.

Changed: Error messages no longer echo the submitted secret. security receives the value as a command-line argument, and a CLI that rejects an argument commonly quotes it back on stderr — that stderr was the thrown error message verbatim. The value is now replaced with [redacted] first. This matters beyond this extension's own spans: the swamp host publishes thrown error messages into its span as a status description, an exception.message, and a stack trace, so an echoed secret reached the trace backend with no instrumentation involved at all.

Still outstanding — the secret is passed as a command-line argument. put invokes security add-generic-password … -w <secret>, and process arguments are readable by other processes running as the same user. Tracked in #275 along with the macOS 26 hex-encoding of find-generic-password -w output. Neither can be verified without a Mac, and a wrong guess breaks the write path for every user.

Note on what spans deliberately omit: spans record error.type and an ERROR status on failure, and never recordException and never a status description. A keychain error message is security's stderr, and the host already publishes it once. Recording key names is intentional — a vault span without the key is close to useless for debugging — so treat key names as visible to anyone with access to your trace backend and do not encode sensitive information in them.

2026.07.26.2

2026.07.26.2

Added: OpenTelemetry spans on get, put, and list, named Keychain get, Keychain put, and Keychain list. Attributes: vault.name, vault.secret_key, vault.service, rpc.system, rpc.service, and rpc.method. list is unsupported by this provider and its span reports ERROR, which is honest: a caller asked for a listing and did not get one.

This closes a real observability gap rather than duplicating the host. swamp emits swamp.vault.* spans when a human runs a swamp vault subcommand, with no attributes at all — and emits nothing when a model or workflow resolves a vault expression. A secret read during a run was invisible in traces. These spans appear on both paths.

The extension uses @opentelemetry/api only and never constructs a TracerProvider. With no provider configured the tracer is a no-op and the cost is a few property lookups.

There are deliberately no spans around the security invocation itself. put passes the secret as the -w argument, so keeping span code out of the exec helper means no edit can attach argv to a span. A test asserts the secret is genuinely present in argv and absent from every span field.

Changed: Error messages no longer echo the submitted secret. security receives the value as a command-line argument, and a CLI that rejects an argument commonly quotes it back on stderr — that stderr was the thrown error message verbatim. The value is now replaced with [redacted] first. This matters beyond this extension's own spans: the swamp host publishes thrown error messages into its span as a status description, an exception.message, and a stack trace, so an echoed secret reached the trace backend with no instrumentation involved at all.

Still outstanding — the secret is passed as a command-line argument. put invokes security add-generic-password … -w <secret>, and process arguments are readable by other processes running as the same user. Tracked in #275 along with the macOS 26 hex-encoding of find-generic-password -w output. Neither can be verified without a Mac, and a wrong guess breaks the write path for every user.

Note on what spans deliberately omit: spans record error.type and an ERROR status on failure, and never recordException and never a status description. A keychain error message is security's stderr, and the host already publishes it once. Recording key names is intentional — a vault span without the key is close to useless for debugging — so treat key names as visible to anyone with access to your trace backend and do not encode sensitive information in them.

updated labels

2026.07.26.1

2026.07.26.1

Fixed: Secrets with leading or trailing whitespace came back altered. The security output was passed through trim(), which strips spaces and tabs at both ends, not just the line terminator the CLI adds. Only a single trailing newline is removed now, so " padded " round-trips intact.

Fixed: Keys are validated before the CLI runs. An empty key produced a confusing security error, and a key starting with - was parsed by security as a flag rather than an account name — put("-U", secret) did not do what it looked like.

Known limitation: A secret whose own final character is a newline cannot be distinguished from the terminator the CLI appends, so that byte is still lost.

Still outstanding — the secret is passed as a command-line argument. put invokes security add-generic-password … -w <secret>, and process arguments are readable by other processes running as the same user, including any monitoring or endpoint agent that records command lines. security offers no documented non-interactive way to supply a password other than argv; the -i interactive mode reads commands from stdin but its quoting rules are undocumented and cannot be exercised without a Mac. This is tracked separately rather than being changed blind, because a wrong guess breaks the write path for every user.

2026.07.18.2

2026.07.18.2

Changed: Version bump only, no code changes.

2026.07.18.1

Changed: Renamed the manifest tags: field to labels: — the schema's actual field name. No runtime or install behavior change.

2026.07.18.1

2026.07.18.1

Changed: Renamed the manifest tags: field to labels: — the schema's actual field name. No runtime or install behavior change.

updated labels

2026.06.15.1
2026.04.22.3
2026.04.22.1
2026.04.13.1
2026.04.03.1
05Stats
A
100 / 100
Downloads
49
Archive size
28.6 KB
  • Has README or module doc2/2earned
  • README has a code example1/1earned
  • README is substantive1/1earned
  • Most symbols documented1/1earned
  • No slow types (deprecated)1/1earned
  • Dependencies pass trust audit2/2earned
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License declared1/1earned
  • Verified public repository2/2earned
06Platforms
07Labels