Back to skill

Security audit

Morning Briefing

Security checks for vulnerabilities and agentic risk

Overview

The morning briefing workflow is mostly coherent, but it ships broader Fulcra and Attio write/delete tools than a read-only briefing skill needs.

Review before installing. This is not malicious on the evidence provided, but install it only if you trust the publisher with sensitive Fulcra data and are comfortable with the package including extra write-capable CRM/Fulcra utilities. Avoid untrusted FULCRA_API_BASE, ATTIO_API_BASE, FULCRA_CLI_COMMAND, or FULCRA_CONCIERGE_HOME settings, and treat the collector output as private.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (22)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
candidates = [[*shlex.split(command), "auth", "print-access-token"]]
    for cmd in candidates:
        try:
            token = subprocess.check_output(
                cmd,
                env=env,
                text=True,
Confidence
83% confidence
Finding
The code executes a command derived from the FULCRA_CLI_COMMAND environment variable using subprocess.check_output after shlex splitting. Although shell injection is avoided by not using shell=True, this still permits execution of an attacker-controlled binary or arguments if the environment is influenced, which can lead to arbitrary code execution in the agent context.

Tainted flow: 'req' from os.environ.get (line 52, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            body = resp.read().decode() or "{}"
            return resp.status, json.loads(body)
    except urllib.error.HTTPError as exc:
Confidence
89% confidence
Finding
The request destination is derived from ATTIO_API_BASE, which comes from the environment and is used directly to build the outbound URL while always attaching the bearer token. If an attacker can influence that environment variable, they can redirect requests and exfiltrate the Attio API key or sensitive CRM data to an arbitrary host; this is especially risky because the module is a reusable CLI/library and the morning-briefing skill only needs trusted read access.

Tainted flow: 'cmd' from os.environ.get (line 32, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
env.setdefault("PYTHONUTF8", "1")
    cmd = [*shlex.split(CLI_COMMAND, posix=(os.name != "nt")), *args]
    try:
        proc = subprocess.run(cmd, env=env, text=True, capture_output=True, timeout=120)
    except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
        return 1, f"CLI invocation failed: {exc}"
    if proc.returncode != 0:
Confidence
97% confidence
Finding
The executable command is derived from the FULCRA_CLI_COMMAND environment variable and then executed. If an attacker can influence the process environment, they can replace the intended Fulcra CLI with an arbitrary program, causing unauthorized code execution under the skill's privileges; using a list does not mitigate this because the tainted value controls the executable itself.

Tainted flow: 'tmp' from os.environ.get (line 53, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
data[name] = value
    # Write atomically-ish and restrict perms before writing the value.
    tmp = SECRETS_PATH.with_suffix(".tmp")
    with open(tmp, "w", encoding="utf-8") as fh:
        json.dump(data, fh, indent=2)
    try:
        os.chmod(tmp, 0o600)
Confidence
87% confidence
Finding
The write path is derived from SECRETS_PATH, which in turn is influenced by the FULCRA_CONCIERGE_HOME environment variable. If an attacker can control that environment variable in the execution context, they can redirect secret writes to an arbitrary location, causing unauthorized file overwrite or placement of credential material outside the intended protected directory. The code also does not validate that the target path remains within a trusted base directory.

Tainted flow: 'req' from os.environ.get (line 82, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(API_BASE + path, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            body = response.read()
            return response.status, body.decode() if body else ""
    except urllib.error.HTTPError as exc:
Confidence
95% confidence
Finding
The request destination is built from FULCRA_API_BASE, which is read from the environment, and the request includes a Bearer token from access_token(). If an attacker can influence that environment variable, the helper will send authenticated requests to an arbitrary host, enabling credential exfiltration and unauthorized API interaction.

Lp3

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding
The skill instructs the agent to run a local Python script and make external requests while having no declared permissions, creating a capability/permission mismatch. In an agent environment, undeclared shell, network, environment, and file-write access reduces oversight and can enable sensitive data exposure or unintended system interaction, especially because the skill handles intimate health and calendar data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented behavior omits collection of heart-rate data and references bundled shared code with broader CRM/Fulcra read-write capabilities than the skill's stated purpose. This over-collection and hidden capability expansion is dangerous because operators may authorize the skill for a narrow morning briefing use case while it can access or modify additional sensitive relationship and annotation data outside user expectations.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
This client exposes create, assert, update, add-note, and delete capabilities even though the morning-briefing skill is described as a read-oriented briefing workflow. Excess write/delete functionality broadens the blast radius: any prompt-injection, misuse by another component, or accidental invocation could modify or delete CRM records unrelated to the skill's purpose.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
This shared helper creates annotation definitions and writes moment records to Fulcra, which exceeds the stated read-only purpose of the morning-briefing skill. In a skill that should only assemble a briefing from existing sleep, calendar, check-in, and debrief data, bundled write primitives increase the chance of accidental or unauthorized state changes if the helper is imported or invoked incorrectly.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
This helper supports creating, updating, deleting, and recording arbitrary Fulcra annotations, which materially exceeds the morning-briefing skill's stated read-and-summarize purpose. In an agent setting, this overbroad capability increases the blast radius of prompt mistakes, tool misuse, or malicious chaining by allowing state mutation where only read access should be necessary.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The file implements commands that mutate annotation definitions and data, conflicting with the manifest's description of delivering a morning briefing from existing sources. This mismatch is dangerous because users and orchestrators may grant the skill broader trust than warranted, while the code can alter or delete persisted user data.

Vague Triggers

Medium
Confidence
86% confidence
Finding
The trigger phrases include broad natural-language requests like 'brief me' and 'what do I have today,' which can cause accidental activation in ordinary conversation. In this skill's context, unintended activation is more concerning because it may pull sensitive sleep, mood, calendar, and weather data and present it when the user may not have intended to invoke a privacy-sensitive workflow.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This script aggregates and prints a single JSON blob containing highly sensitive personal data, including sleep metrics, calendar details, heart-rate data, subjective check-in content, and open action items. Even if intended for normal skill operation, emitting all of this to stdout without any in-code warning, minimization, or confirmation increases the risk of accidental exposure through logs, debugging output, shell history capture, agent transcripts, or downstream consumers that persist command output.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def run_cli(args: list[str]) -> tuple[int, str]:
    env = os.environ.copy()
    env.setdefault("PYTHONUTF8", "1")
    cmd = [*shlex.split(CLI_COMMAND, posix=(os.name != "nt")), *args]
    try:
Confidence
60% confidence
Finding
Code accesses environment variables that may contain secrets (API keys, tokens). This is a common pattern for credential theft.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def access_token() -> str:
    env_token = os.environ.get("FULCRA_ACCESS_TOKEN")
    if env_token:
        return env_token.strip()
Confidence
70% confidence
Finding
Code accesses environment variables that may contain secrets (API keys, tokens). This is a common pattern for credential theft.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
if env_token:
        return env_token.strip()

    env = os.environ.copy()
    env["HOME"] = DEFAULT_HOME

    command = os.environ.get("FULCRA_CLI_COMMAND", "uv tool run fulcra-api")
Confidence
60% confidence
Finding
Code accesses environment variables that may contain secrets (API keys, tokens). This is a common pattern for credential theft.

Unvalidated Output Injection

High
Category
Output Handling
Content
env.setdefault("PYTHONUTF8", "1")
    cmd = [*shlex.split(CLI_COMMAND, posix=(os.name != "nt")), *args]
    try:
        proc = subprocess.run(cmd, env=env, text=True, capture_output=True, timeout=120)
    except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
        return 1, f"CLI invocation failed: {exc}"
    if proc.returncode != 0:
Confidence
95% confidence
Finding
Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.

Credential Access

High
Category
Privilege Escalation
Content
stdlib-only (urllib) so it runs under any `uv run`/python without extra installs,
mirroring the Fulcra annotation helper. It is BOTH a library (import the functions)
and a CLI (skills shell out to it). Every write supports --dry-run, and the token is
loaded via concierge_secrets (env ATTIO_API_KEY or ~/.fulcra-concierge/secrets.json) and
never printed.

Attio shapes used (verify against the live API; envelopes are the documented ones):
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. ~/.fulcra-concierge/secrets.json -- the persistent local store.

Secrets are never printed. Skills read keys through `get_secret`; they should
not open secrets.json themselves. Keeping one loader means one place to harden
(file perms, future keychain support) instead of N copies across skills.
"""
from __future__ import annotations
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
import os
from pathlib import Path

SECRETS_PATH = Path(os.environ.get("FULCRA_CONCIERGE_HOME", Path.home() / ".fulcra-concierge")) / "secrets.json"


def _load_file() -> dict:
Confidence
81% confidence
Finding
This line defines a persistent plaintext secrets file location, with its parent directory controlled by FULCRA_CONCIERGE_HOME. Storing credentials in a JSON file is not automatically malicious, but it is a real security weakness because compromise of the local account, misconfigured filesystem permissions, or path redirection can expose credentials. The skill context makes this somewhat more sensitive because this module is a shared secret loader used across concierge skills, so one weakness affects multiple integrations.

Credential Access

High
Category
Privilege Escalation
Content
def set_secret(name: str, value: str) -> None:
    """Persist a secret into secrets.json (merging with any existing keys) and
    lock the file down to the current user. Used by setup helpers, not skills."""
    SECRETS_PATH.parent.mkdir(parents=True, exist_ok=True)
    data = _load_file()
Confidence
79% confidence
Finding
This function persists secrets into a local JSON file, which creates a durable plaintext credential store on disk. Although it attempts to chmod the temporary file, the design still increases credential exposure risk through backups, local compromise, redirected storage paths, or platforms where chmod is ineffective or skipped.

Credential Access

High
Category
Privilege Escalation
Content
Secrets are never printed. Skills read keys through `get_secret`; they should
not open secrets.json themselves. Keeping one loader means one place to harden
(file perms, future keychain support) instead of N copies across skills.
"""
from __future__ import annotations
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

No suspicious patterns detected.