Back to skill

Security audit

Keychain Bridge Publish

Security checks for vulnerabilities and agentic risk

Overview

This local Keychain utility is purpose-aligned, but it handles high-impact secrets with unsafe file fallback, plaintext bridge, and copy-pastable credential-handling patterns that need human review.

Review before installing. This skill appears local and non-exfiltrating, but it should not be used for valuable credentials until service names are path-validated, plaintext bridge files are opt-in and securely created, unsafe password and secret examples are removed, and dependency installation is pinned or isolated.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/keychain_helper.py:14
Finding

Path Traversal in Secret File Fallback Allows Arbitrary Local File Disclosure

Content
View full analysis

Vulnerability Details

File Location: scripts/keychain_helper.py:14-30; attacker-controlled input originates at scripts/get_secret.py:21
Vulnerability Type: Path traversal and arbitrary file read
Risk Level: High

Vulnerable Code

scripts/get_secret.py:21:

python
service = sys.argv[1]

scripts/keychain_helper.py:14-30:

python
def get_secret(service, account=None, secrets_dir=None):
    """Get a secret from keychain. Falls back to secrets/ file."""
    acct = account or ACCOUNT
    sdir = secrets_dir or SECRETS_DIR
    try:
        import keyring
        value = keyring.get_password(service, acct)
        if value:
            return value
    except Exception:
        pass
    # Fallback: read from file
    path = os.path.join(sdir, service)
    try:
        with open(path) as f:
            return f.read().strip()
    except Exception:
        return ""

Technical Analysis

The command-line service argument is passed directly to os.path.join() without validating that it is a simple service name. Absolute paths cause Python to discard the intended secrets-directory prefix, while ../ components can traverse outside that directory.

When the corresponding Keychain lookup returns no value or raises an exception, the helper silently opens the constructed path. The CLI then prints the returned contents to standard output. Catching all Keychain exceptions makes the filesystem fallback especially easy to reach and hides the reason that the security boundary was bypassed.

Attack Path

  1. An attacker able to control arguments passed to get_secret.py supplies an absolute path or traversal sequence as the service name.
  2. For example, the attacker requests a value such as ../../../.ssh/id_rsa.
  3. The Keychain lookup does not return a matching item.
  4. os.path.join(sdir, service) resolves outside ~/.openclaw/secrets.
  5. The helper read ...[truncated 686 chars]
Remediation
View remediation

Remediation Suggestions

  • Require service identifiers to match a restrictive allowlist, such as ^[A-Za-z0-9._-]+$.
  • Reject absolute paths, path separators, null characters, . components, and .. components.
  • Resolve both the configured directory and candidate path, then verify that the candidate's parent is the resolved secrets directory.
  • Consider replacing arbitrary service names with an explicit map of permitted service identifiers to files.
  • Do not silently fall back after every Keychain exception. Distinguish “item not found” from access-control or backend failures.
  • Return structured errors rather than an empty string so callers can detect and handle security failures.

Example containment logic:

python
from pathlib import Path
import re

if not re.fullmatch(r"[A-Za-z0-9._-]+", service):
    raise ValueError("Invalid service name")

base = Path(sdir).expanduser().resolve()
candidate = (base / service).resolve()
if candidate.parent != base:
    raise ValueError("Service path escapes secrets directory")

T09 · Insecure Skill Coding Practices

Error
Location
scripts/migrate_secrets.py:72
Finding

Plaintext Secrets Are Written to Umask-Dependent Temporary Files During Migration

Content
View full analysis

Vulnerability Details

File Location: scripts/migrate_secrets.py:72-98
Vulnerability Type: Insecure temporary-file handling and plaintext credential exposure
Risk Level: High

Vulnerable Code

python
def inject_secret(python_path, service, account, value):
    """Inject a secret into keychain using a specific Python binary."""
    # Use a temp file to avoid shell escaping issues with the value
    import tempfile
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        f.write(f"""
import keyring
keyring.set_password({service!r}, {account!r}, open({f.name + '.val'!r}).read())
""")
        script_path = f.name
    val_path = script_path + '.val'
    try:
        with open(val_path, 'w') as vf:
            vf.write(value)
        result = subprocess.run(
            [python_path, script_path],
            capture_output=True, text=True, timeout=30
        )
        return result.returncode == 0, result.stderr.strip()
    except subprocess.TimeoutExpired:
        return False, "timeout (30s) — may indicate keychain access issue"
    except Exception as e:
        return False, str(e)
    finally:
        for p in [script_path, val_path]:
            try:
                os.unlink(p)
            except OSError:
                pass

Technical Analysis

NamedTemporaryFile securely creates the temporary Python script, but the adjacent .val file is subsequently created using ordinary open(val_path, 'w'). Its initial permissions depend on the process umask. Under a common umask of 022, the file may be created as mode 0644, exposing its plaintext contents to other local users while migration is in progress.

The .val filename is derived predictably from the temporary script filename and is created in a separate operation. This creates a race window and does not use exclusive creation. Cleanup only occurs after execution re ...[truncated 1463 chars]

Remediation
View remediation

Remediation Suggestions

  • Prefer transmitting the secret through the child process's standard input while using a fixed Python program.
  • Do not place secret values in command-line arguments, generated source code, or environment variables.
  • If a temporary file is unavoidable, create a private temporary directory with mode 0700.
  • Create the value file atomically with os.open() using O_CREAT | O_EXCL and mode 0600.
  • Open the file descriptor directly rather than deriving and reopening a predictable filename.
  • Ensure cleanup occurs through robust context managers and document that deletion does not guarantee secure erasure on modern filesystems.
  • Limit migration to explicitly selected, trusted Python interpreters rather than automatically exposing secrets to all detected pyenv installations.

A safer subprocess pattern is:

python
fixed_program = (
    "import sys, keyring; "
    "keyring.set_password(sys.argv[1], sys.argv[2], sys.stdin.read())"
)
result = subprocess.run(
    [python_path, "-c", fixed_program, service, account],
    input=value,
    text=True,
    capture_output=True,
    timeout=30,
)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:223
Finding

Documented Cross-Interpreter Migration Procedure Enables Python Code Injection

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:223-228
Vulnerability Type: Code injection through unsafe source-code interpolation
Risk Level: High

Vulnerable Code

python
# Read from working version, write via target version
import subprocess, keyring
value = keyring.get_password("service", "account")
subprocess.run(["/opt/homebrew/bin/python3.14", "-c",
    f"import keyring; keyring.set_password('service', 'account', '{value}')"])

Technical Analysis

The documentation inserts a secret value directly into Python source passed to the target interpreter's -c option. A value containing a quote, backslash, newline, or Python expression can terminate the intended string literal and append arbitrary Python statements.

Although this pattern appears in documentation rather than an automatically invoked script, the Skill explicitly instructs an agent or user to execute it as a workaround for cross-interpreter Keychain ACL behavior. Following the documented procedure with an attacker-controlled Keychain value therefore executes injected code.

Using an argument array avoids shell parsing but does not prevent Python-language injection because the vulnerable parser is the target Python interpreter itself.

Attack Path

  1. An attacker controls or modifies the source Keychain value, or supplies a credential whose legitimate value contains source-significant characters.
  2. A user or agent follows the documented cross-Python migration procedure.
  3. The f-string embeds the value into the -c source program without safe serialization.
  4. The crafted value closes the string literal and appends arbitrary Python statements.
  5. The target Python interpreter executes those statements with the privileges of the user running the migration.

Impact Assessment

Exploitation permits arbitrary local code execution as the invoking user. The injected program can read user files, modify OpenClaw configu ...[truncated 327 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove the vulnerable example from SKILL.md.
  • Use a fixed, non-interpolated Python program and provide the secret through standard input.
  • Pass service and account identifiers as separate argv elements.
  • If source serialization is ever unavoidable, use a correct serializer such as repr(), but standard input remains preferable for secrets.
  • Add tests with quotes, backslashes, newlines, and code-like values to ensure they remain data.
  • Avoid printing or logging the constructed process arguments.

Safe replacement:

python
program = (
    "import sys, keyring; "
    "keyring.set_password(sys.argv[1], sys.argv[2], sys.stdin.read())"
)
subprocess.run(
    ["/opt/homebrew/bin/python3.14", "-c", program, "service", "account"],
    input=value,
    text=True,
    check=True,
)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/populate_secrets.sh:6
Finding

Boot-Time Bridge Creates Sensitive Files Before Enforcing Secure Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/populate_secrets.sh:6-31
Vulnerability Type: Plaintext secret persistence, permission race, and non-atomic file writes
Risk Level: Medium

Vulnerable Code

bash
SECRETS_DIR="${SECRETS_DIR:-$HOME/.openclaw/secrets}"
ACCOUNT="${ACCOUNT:-moltbot}"

# --- Group B services (expected on disk) ---
GROUP_B_SERVICES=(
    "arr-api-keys"
    "claude-code-token"
    "clawmarket-wallet-key"
    "gitea-api"
    "google-ai-api-key"
    "ha-token"
    "hooks-token"
    "influxdb-token"
    "openrouter-api-key"
)

mkdir -p "$SECRETS_DIR"

populated=0
failed=0

for svc in "${GROUP_B_SERVICES[@]}"; do
    value=$(python3 "$SCRIPT_DIR/get_secret.py" "$svc" --account "$ACCOUNT" 2>/dev/null)
    if [ -n "$value" ]; then
        echo -n "$value" > "$SECRETS_DIR/$svc"
        chmod 600 "$SECRETS_DIR/$svc"
        populated=$((populated + 1))
    else
        echo "WARNING: $svc not found in keychain" >&2
        failed=$((failed + 1))
    fi
done

Technical Analysis

The script intentionally materializes nine secrets, including a wallet key, as persistent plaintext files. mkdir -p does not explicitly enforce mode 0700, and new files are opened by shell redirection before chmod 600 runs. Their initial permissions therefore depend on the process umask.

The write and permission change are separate operations, creating a disclosure window. Direct redirection is non-atomic, so readers may observe truncated or partially written credentials. It also follows existing symbolic links, which can result in unintended file modification if an attacker can write within the target directory.

This behavior conflicts with the broad claim that Keychain migration eliminates plaintext credential storage. The bridge may be necessary for legacy consumers, but applying it by default to highly sensitive wallet material exceeds the minimum safe exposur ...[truncated 1236 chars]

Remediation
View remediation

Remediation Suggestions

  • Set umask 077 before creating any directory or file.
  • Create the secrets directory with mode 0700 and verify that it is owned by the expected user.
  • Refuse to operate when the directory or destination is a symbolic link.
  • Create temporary files atomically with mode 0600, write and flush the contents, then rename them into place.
  • Remove wallet keys and similarly high-impact credentials from the default Group B list.
  • Make file bridging opt-in per service and document the resulting plaintext exposure.
  • Delete bridge files as soon as the consuming process has loaded them.
  • Prefer direct Keychain integration or inherited file descriptors for consumers that can support them.
  • Avoid retaining secret values in shell variables where possible.

At minimum, initialize the script with:

bash
umask 077
mkdir -p -- "$SECRETS_DIR"
chmod 700 -- "$SECRETS_DIR"

T08 · Insecure Dependencies

Warning
Location
SKILL.md:47
Finding

Unpinned Third-Party Keyring Installation Creates Supply-Chain Risk

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:47-53 and SKILL.md:234-241; also documented in README.md:62
Vulnerability Type: Unpinned dependency installation from a mutable package source
Risk Level: Medium

Vulnerable Code

bash
pip3 install keyring
# If multiple Python versions exist (common on macOS):
/usr/bin/python3 -m pip install keyring
/opt/homebrew/opt/python@3.14/bin/python3.14 -m pip install --break-system-packages keyring

A later section repeats the installation procedure:

bash
# Install for system Python
/usr/bin/python3 -m pip install keyring
# Install for Homebrew Python
/opt/homebrew/opt/python@3.14/bin/python3.14 -m pip install --break-system-packages keyring

Technical Analysis

The Skill instructs users to install the latest available keyring package without pinning an audited version or verifying package hashes. The final package and transitive dependency set can therefore change after the Skill has been reviewed.

Installing into multiple Python environments increases the affected surface. The --break-system-packages option also bypasses package-manager protections for the Homebrew interpreter and may destabilize or unexpectedly alter a shared environment.

No evidence was found that the named keyring package is malicious. The issue is that the installation procedure lacks reproducibility and integrity controls.

Attack Path

  1. A user follows the Skill prerequisite instructions.
  2. pip resolves the current package and transitive dependencies from its configured index.
  3. A compromised upstream release, package index, mirror, or dependency is selected.
  4. Package installation or subsequent import executes unreviewed code.
  5. Because the library is used to process Keychain credentials, compromised dependency code may access secrets available to the invoking account.

Impact Assessment

A compromised dependency executes with ...[truncated 473 chars]

Remediation
View remediation

Remediation Suggestions

  • Pin an audited keyring version and all transitive dependencies.
  • Publish a locked requirements file containing cryptographic hashes.
  • Install with pip --require-hashes from a documented, trusted package index.
  • Prefer an isolated virtual environment rather than modifying system or shared Homebrew interpreters.
  • Remove or narrowly justify the use of --break-system-packages.
  • Define a tested upgrade and review process before changing dependency versions.
  • Record expected package provenance and verify downloaded artifacts in deployment documentation.

Example policy:

bash
python3 -m venv ~/.openclaw/venvs/keychain-bridge
~/.openclaw/venvs/keychain-bridge/bin/python -m pip install \
    --require-hashes -r requirements.lock
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (144)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The skill claims broad secret-management, auditing, bridging, and diagnostic capabilities, but the static analysis indicates the implementation may not actually provide those controls. Description/behavior mismatches are dangerous because operators may trust the skill to migrate, audit, or protect secrets when those protections are absent or incomplete.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

This skill is explicitly designed to access, store, and migrate credentials, including API keys. Credential-access functionality is inherently high risk because misuse, accidental invocation, or weak guardrails can expose secrets or persist them in less secure locations; the danger is amplified here by the described fallback to filesystem bridge files.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
name: keychain-bridge
description: Manage secrets via macOS Keychain instead of plaintext files. Migrate existing secrets, read/write keychain entries, bridge to files for bash tools, audit for leaks, diagnose access issues. Use when asked about secrets, keychain, credentials, API keys, or security hardening on macOS.
homepage: https://github.com/moltbot/keychain-bridge
metadata:
  openclaw:

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

Triggering on generic phrases like 'get API key' exposes a direct credential-access surface. In context, broad invocation criteria make this more dangerous because the skill may perform sensitive retrieval steps based on ambiguous requests.

Content

Scanner excerpt · SKILL.md (reported line 27)May include surrounding context.

md
- "check keychain health" / "keychain status"
- "audit secrets" / "check for leaks"
- "read secret" / "get API key"
- "store secret" / "write to keychain"
- "keychain not working" / "security find-generic-password hangs"

## Example Usage

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

The example workflow migrates plaintext secrets into a managed store, which necessarily handles sensitive material. Secret migration is legitimate but security-sensitive, and if implemented unsafely it can duplicate secrets, leave remnants on disk, or expose them through process arguments or logs.

Content

Scanner excerpt · SKILL.md (reported line 33)May include surrounding context.

Example Usage

text
User: "Migrate my secrets to the keychain"
Action: python3 SKILL_DIR/scripts/migrate_secrets.py --dir ~/.openclaw/secrets/ --account moltbot --dry-run

User: "Check if the keychain bridge is healthy"

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

Auditing for plaintext secret leaks requires enumerating potentially sensitive files and correlating them with keychain entries. That capability is legitimate but sensitive, and can reveal the presence, naming, or storage pattern of credentials if exposed too broadly.

Content

Scanner excerpt · SKILL.md (reported line 37)May include surrounding context.

md
Action: python3 SKILL_DIR/scripts/migrate_secrets.py --dir ~/.openclaw/secrets/ --account moltbot --dry-run

User: "Check if the keychain bridge is healthy"
Action: Run keychain health check (test write/read/delete cycle)

User: "Audit for plaintext secret leaks"
Action: python3 SKILL_DIR/scripts/audit_secrets.py --dir ~/.openclaw/secrets/ --account moltbot

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 47)May include surrounding context.

Prerequisites

The keyring Python library must be installed for each Python version that will access secrets:

bash
pip3 install keyring

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

The Group B bridge intentionally reads secrets from the keychain and writes them back to plaintext files for bash consumption. Even with chmod 600, materializing secrets on disk expands the attack surface to local compromise, backups, indexing, accidental copies, and operational mistakes; the skill context makes this especially dangerous because it normalizes degrading storage security to work around platform issues.

Content

Scanner excerpt · SKILL.md (reported line 103)May include surrounding context.

bash SKILL_DIR/scripts/populate_secrets.sh

text

This reads each Group B secret from keychain and writes it to a `chmod 600` file that bash scripts can `cat`.

## Read a Secret

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The helper is documented to try keychain first and then fall back to file reads, which can silently weaken security guarantees. Consumers may believe they are using keychain-backed secrets when in fact they are accepting plaintext files, increasing the chance of unnoticed exposure.

Content

Scanner excerpt · SKILL.md (reported line 111)May include surrounding context.

python
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from keychain_helper import get_secret

token = get_secret("my-service-name")

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The example injects secret values directly into a shell command line (python -c ... 'VALUE'), which can expose secrets through shell history, process listings, audit logs, or command tracing. Because the skill centers on credential handling, this is a concrete unsafe handling pattern rather than merely sensitive functionality.

Content

Scanner excerpt · SKILL.md (reported line 143)May include surrounding context.

Inject from each

for py in "${PYTHONS[@]}"; do $py -c "import keyring; keyring.set_password('SERVICE', 'ACCOUNT', 'VALUE')" done

text

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The sample shows a Python process that loads the Security framework and unlocks the keychain for subsequent secret writes. Guidance that encourages embedding unlock flows into ad hoc scripts raises the risk of mishandling authentication material and normalizes bypass-style workflows for credential access.

Content

Scanner excerpt · SKILL.md (reported line 179)May include surrounding context.

python
python3 << 'PYEOF'
import ctypes, ctypes.util, keyring

# Unlock via Security framework (bypasses broken security CLI)
Security = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))

Credential Access

High
Category
Privilege Escalation
Confidence
98% confidence
Finding

The example includes a hardcoded path to the login keychain and, more importantly, a literal placeholder for the user's login password in script source. Encouraging users to place their login password into an inline script is extremely dangerous because it can leak via shell history, saved files, terminal scrollback, screenshots, and process/environment inspection.

Content

Scanner excerpt · SKILL.md (reported line 183)May include surrounding context.

md
# Unlock via Security framework (bypasses broken security CLI)
Security = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
keychain = ctypes.c_void_p()
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

Calling SecKeychainUnlock with a password buffer in an example script demonstrates direct handling of the user's login credential in process memory and code. In a secret-management skill, this is highly sensitive and can enable compromise far beyond a single API key if the login password is exposed.

Content

Scanner excerpt · SKILL.md (reported line 184)May include surrounding context.

md
# Unlock via Security framework (bypasses broken security CLI)
Security = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
keychain = ctypes.c_void_p()
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

Calling SecKeychainUnlock with a password buffer in an example script demonstrates direct handling of the user's login credential in process memory and code. In a secret-management skill, this is highly sensitive and can enable compromise far beyond a single API key if the login password is exposed.

Content

Scanner excerpt · SKILL.md (reported line 184)May include surrounding context.

md
# Unlock via Security framework (bypasses broken security CLI)
Security = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
keychain = ctypes.c_void_p()
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

This line continues the unsafe unlock flow with the user's keychain password. The danger is not merely keychain access but potential credential theft of the account's login secret, which could cascade into full local account compromise.

Content

Scanner excerpt · SKILL.md (reported line 185)May include surrounding context.

md
Security = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
keychain = ctypes.c_void_p()
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

This line continues the unsafe unlock flow with the user's keychain password. The danger is not merely keychain access but potential credential theft of the account's login secret, which could cascade into full local account compromise.

Content

Scanner excerpt · SKILL.md (reported line 185)May include surrounding context.

md
Security = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
keychain = ctypes.c_void_p()
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

After unlocking, the sample immediately writes a secret value to the keychain within the same process. That is expected functionality, but in this unsafe context it couples login-password handling with secret injection, increasing blast radius if the script or terminal session is observed or logged.

Content

Scanner excerpt · SKILL.md (reported line 187)May include surrounding context.

md
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

# Now keyring works — but ONLY within this same process
keyring.set_password("SERVICE", "ACCOUNT", "VALUE")

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

After unlocking, the sample immediately writes a secret value to the keychain within the same process. That is expected functionality, but in this unsafe context it couples login-password handling with secret injection, increasing blast radius if the script or terminal session is observed or logged.

Content

Scanner excerpt · SKILL.md (reported line 187)May include surrounding context.

md
path = b"/Users/USERNAME/Library/Keychains/login.keychain-db"
Security.SecKeychainOpen(path, ctypes.byref(keychain))
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

# Now keyring works — but ONLY within this same process
keyring.set_password("SERVICE", "ACCOUNT", "VALUE")

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The example sets a secret using a literal VALUE in code after an unlock sequence. Inline secret literals in example scripts encourage patterns that leak through saved commands, copied snippets, and debugging output.

Content

Scanner excerpt · SKILL.md (reported line 189)May include surrounding context.

md
pw = b"YOUR_LOGIN_PASSWORD"
Security.SecKeychainUnlock(keychain, ctypes.c_uint32(len(pw)), pw, ctypes.c_bool(True))

# Now keyring works — but ONLY within this same process
keyring.set_password("SERVICE", "ACCOUNT", "VALUE")
print("OK" if keyring.get_password("SERVICE", "ACCOUNT") else "FAIL")
PYEOF

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

The documented workaround explicitly retrieves a secret from the keychain and writes it to a plaintext bridge file for SSH-only access. This weakens the security posture from protected storage to filesystem secrecy, making theft easier through local access, backup leakage, or other file exposure paths.

Content

Scanner excerpt · SKILL.md (reported line 202)May include surrounding context.

  • If you need SSH-only access to the secret after writing, create a Group B bridge file in the same process:
python
# After keyring.set_password() succeeds in the same process:
import os
val = keyring.get_password("SERVICE", "ACCOUNT")
os.makedirs(os.path.expanduser("~/.my-app/secrets"), exist_ok=True)

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

Writing the retrieved secret value directly with open(path, 'w') creates a plaintext secret at rest. In context, this is an intentional downgrade from keychain protections, so it is a true security risk even though it is presented as a workaround.

Content

Scanner excerpt · SKILL.md (reported line 204)May include surrounding context.

python
# After keyring.set_password() succeeds in the same process:
import os
val = keyring.get_password("SERVICE", "ACCOUNT")
os.makedirs(os.path.expanduser("~/.my-app/secrets"), exist_ok=True)
path = os.path.expanduser("~/.my-app/secrets/SERVICE")
with open(path, "w") as f:

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

The example copies a secret between Python versions by interpolating the secret into a subprocess.run(..., '-c', f"...{value}...") command string. This exposes the secret in process arguments and is additionally injection-prone if the secret contains quotes or Python syntax, potentially causing code execution or corruption.

Content

Scanner excerpt · SKILL.md (reported line 225)May include surrounding context.

Fix: Inject from both Python versions:

python
# Read from working version, write via target version
import subprocess, keyring
value = keyring.get_password("service", "account")
subprocess.run(["/opt/homebrew/bin/python3.14", "-c",
    f"import keyring; keyring.set_password('service', 'account', '{value}')"])

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

Reading the secret for later interpolation into a generated Python command is part of the same unsafe transfer pattern. The main danger is the subsequent argument/code injection and exposure of the secret value outside protected storage.

Content

Scanner excerpt · SKILL.md (reported line 226)May include surrounding context.

python
# Read from working version, write via target version
import subprocess, keyring
value = keyring.get_password("service", "account")
subprocess.run(["/opt/homebrew/bin/python3.14", "-c",
    f"import keyring; keyring.set_password('service', 'account', '{value}')"])

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

This line shows the actual generated command embedding the secret value, which is both a secret-exposure risk and a code-injection risk if the value is not safely encoded. In a credential-management tool, this is a concrete unsafe example that could be copied verbatim by users or other agents.

Content

Scanner excerpt · SKILL.md (reported line 228)May include surrounding context.

import subprocess, keyring value = keyring.get_password("service", "account") subprocess.run(["/opt/homebrew/bin/python3.14", "-c", f"import keyring; keyring.set_password('service', 'account', '{value}')"])

text

Or use `migrate_secrets.py` which handles this automatically.

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The diagram explicitly depicts a file-bridge path where a boot process writes secret files for bash. This architectural choice expands secret exposure from protected keystore access to persistent plaintext-on-disk access, which is a meaningful vulnerability surface in context.

Content

Scanner excerpt · SKILL.md (reported line 260)May include surrounding context.

md
│   (keychain only)  │     │     │   (file bridge)      │
    │                    │     │     │                      │
    │ Python scripts     │     │     │ populate_secrets.sh  │
    │ import keychain_   │     │     │ runs at boot →       │
    │ helper.get_secret()│     │     │ writes chmod 600     │
    │                    │     │     │ files for bash       │
    └────────────────────┘     │     └──────────────────────┘

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

A fallback that silently reads from files after keychain failure can mask degraded security and normalize plaintext storage. The risk is not the fallback existing at all, but that it may happen implicitly without strong signaling or user intent.

Content

Scanner excerpt · SKILL.md (reported line 268)May include surrounding context.

┌──────────▼──────────┐ │ Fallback │ │ get_secret() tries │ │ keychain first, │ │ then file read │ └─────────────────────┘

text

Static analysis

No suspicious patterns detected.