Back to skill

Security audit

Personal Concierge

Security checks for vulnerabilities and agentic risk

Overview

This concierge skill has a coherent purpose, but it needs Review because its routine checks and shared helpers can use sensitive credentials through mutable local code, unpinned external tools, broad environment overrides, and high-impact data mutation commands.

Install only if you trust the local Fulcra concierge directory, the Fulcra CLI supply chain, and the environment that launches your agent. Before use, confirm API base environment variables point only to the intended Fulcra and Attio hosts, avoid broad automatic invocation for sensitive workflows, and treat the included delete/update helpers as privileged tools that should require explicit user intent.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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
Findings (3)

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/status.py:41
Finding

External Module Path Precedence Allows Tool Hijacking During Status Checks

Content
View full analysis
dict: try: sys.path.insert(0, str(Path.home() / ".fulcra-concierge" / "lib")) import fulcra_annotations as fa tok = fa.access_token() # raises/exits if unavailable return {"available": bool(tok)} except SystemExit: return {"available": False, "reason": "no Fulcra token (run: uv tool run fulcra-api auth login)"} except Exception as exc: return {"available": False, "reason": str(exc)[:200]} ``` The bundled bootstrap logic normally prefers the library shipped with the Skill: ```python for _c in _candidates(): if (_c / "concierge_cli.py").exists() and str(_c) not in sys.path: sys.path.insert(0, str(_c)) break ``` However, `status.py` subsequently inserts `~/.fulcra-concierge/lib` at index zero immediately before importing `fulcra_annotations`, overriding the bundled-library precedence. ### Technical Analysis Python executes the top-level code of a module when it is imported. By prepending a mutable external directory to `sys.path`, the status check allows a file named `fulcra_annotations.py` in that directory to replace the reviewed implementation bundled under `scripts/lib`. The code does not verify the imported module's resolved path, ownership, permissions, integrity, or signature. Consequently, the legitimate-looking call to `fa.access_token()` can invoke attacker-controlled Python code. This is especially significant because `SKILL.md` directs the Agent to run the status command before orchestrating other concierge functionality. The vulnerable import therefore lies on a routine execution path rather than an obscure administrative path. ### Attack Path 1. An attacker or compromised local process creates: `~/.fulcra-concierge/lib/fulcra_annotations.py`. 2. The ...[truncated 1287 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/lib/fulcra_annotations.py:50
Finding

Unpinned Fulcra CLI Is Retrieved and Executed at Runtime

Content
View full analysis
str: env_token = os.environ.get("FULCRA_ACCESS_TOKEN") 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") candidates = [[*shlex.split(command), "auth", "print-access-token"]] for cmd in candidates: try: token = subprocess.check_output( cmd, env=env, text=True, stderr=subprocess.DEVNULL, timeout=45, ).strip() except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired): continue ``` The shared CLI wrapper uses the same unpinned default: ```python CLI_COMMAND = os.environ.get("FULCRA_CLI_COMMAND", "uv tool run fulcra-api") ``` ```python 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: proc = subprocess.run(cmd, env=env, text=True, capture_output=True, timeout=120) ``` The documented workflow invokes the status check: ```bash uv run --python 3.12 ~/.claude/skills/concierge-wrapper/scripts/status.py ``` That status check calls `access_token()`, making dependency execution part of the normal readiness probe. ### Technical Analysis The command `uv tool run fulcra-api` does not specify an exact package version, artifact hash, lockfile, or verified source. Depending on the local `uv` configuration and cache state, it may resolve and ...[truncated 2162 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/lib/attio_client.py:34
Finding

Configurable API Origins Can Redirect Bearer Tokens and Sensitive Data

Content
View full analysis
tuple[int, Any]: token = get_secret("ATTIO_API_KEY", required=True) url = API_BASE + path if params: url += "?" + urllib.parse.urlencode(params) data = json.dumps(payload).encode() if payload is not None else None headers = {"Authorization": f"Bearer {token}"} if data is not None: 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: ``` The Fulcra client implements the same pattern: ```python API_BASE = os.environ.get("FULCRA_API_BASE", "https://api.fulcradynamics.com").rstrip("/") ``` ```python def request(method: str, path: str, payload: Any | None = None) -> tuple[int, str]: data = None headers = {"Authorization": f"Bearer {access_token()}"} if payload is not None: data = json.dumps(payload).encode() 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: ``` ### Technical Analysis `ATTIO_API_BASE` and `FULCRA_API_BASE` are trusted without validating their URL scheme, hostname, port, or relationship to the intended service. Both clients then attach bearer credentials to requests sent to the resulting origin. If an attacke ...[truncated 2230 chars]
Remediation
View remediation
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (33)

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

Critical
Category
Data Flow
Confidence
97% confidence
Finding

The request destination is derived from ATTIO_API_BASE, which is taken directly from the environment and used to build the outbound URL without validation. If an attacker can influence the runtime environment, they can redirect authenticated requests carrying the Bearer token to an attacker-controlled host, causing credential exfiltration and unauthorized API actions. In a concierge/orchestration skill that handles CRM data, this is more dangerous because the client is a shared entry point used by other skills and can expose broad contact and notes data.

Content

Scanner excerpt · scripts/lib/attio_client.py (reported line 54)May include surrounding context.

python
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:

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

Critical
Category
Data Flow
Confidence
98% confidence
Finding

API_BASE is sourced from the environment and concatenated into outbound requests that include a Bearer token in the Authorization header. If an attacker can set FULCRA_API_BASE, they can redirect all authenticated traffic to an attacker-controlled server and exfiltrate the access token plus any annotation data. For a concierge/orchestration skill handling personal data, this context makes the issue especially dangerous.

Content

Scanner excerpt · scripts/lib/fulcra_annotations.py (reported line 84)May include surrounding context.

python
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:

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented end-user behavior is broad concierge orchestration, but the implementation reportedly performs environment/install/status inspection and probes Fulcra auth and Attio configuration instead of the promised flow execution. While status checks are not inherently malicious, undisclosed probing of local configuration and auth state can leak operational details and normalize hidden environment inspection under an unrelated trigger.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/lib/concierge_cli.py (reported line 42)May include surrounding context.

python
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:

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/lib/fulcra_annotations.py (reported line 55)May include surrounding context.

python
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:

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 · scripts/lib/attio_client.py (reported line 7)May include surrounding context.

python
Resolution order for any key:
  1. Environment variable (e.g. ATTIO_API_KEY) -- wins, good for CI / one-offs.
  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

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 · scripts/lib/concierge_secrets.py (reported line 5)May include surrounding context.

python
Resolution order for any key:
  1. Environment variable (e.g. ATTIO_API_KEY) -- wins, good for CI / one-offs.
  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

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 · scripts/lib/concierge_secrets.py (reported line 8)May include surrounding context.

python
Resolution order for any key:
  1. Environment variable (e.g. ATTIO_API_KEY) -- wins, good for CI / one-offs.
  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

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 · scripts/lib/concierge_secrets.py (reported line 17)May include surrounding context.

python
Resolution order for any key:
  1. Environment variable (e.g. ATTIO_API_KEY) -- wins, good for CI / one-offs.
  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

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 · scripts/lib/concierge_secrets.py (reported line 47)May include surrounding context.

python
Resolution order for any key:
  1. Environment variable (e.g. ATTIO_API_KEY) -- wins, good for CI / one-offs.
  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

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 · scripts/lib/concierge_secrets.py (reported line 9)May include surrounding context.

python
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

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README advertises very broad trigger phrases such as "start my day" and "status check," which are common natural-language requests that can be uttered in unrelated contexts. Because this skill is an orchestrator that routes into multiple other concierge skills and may initiate multi-step flows, accidental or context-injected invocation could cause unintended actions, data access, or disclosure across downstream skills.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill invokes a shell command (uv run ... status.py) and the surrounding documentation indicates capabilities involving environment access, filesystem interaction, and network-dependent checks, yet it declares no explicit tool scope or permission boundaries. In an orchestration skill handling intimate user data, this lack of least-privilege constraints increases the risk of unintended command execution, secret exposure, or overbroad access if the skill is invoked in the wrong context.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases are very broad ('run my concierge', 'be my concierge', requests spanning several skills), which can cause accidental invocation of a skill that may in turn access sensitive health, calendar, relationship, and CRM-related data. In this context, overbroad routing is more dangerous than usual because the wrapper claims to chain multiple sub-skills and potentially store personal information as part of a single utterance.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/lib/attio_client.py (reported line 6)May include surrounding context.

python
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.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This library exposes a destructive delete_record function and CLI command even though the concierge-wrapper's stated role is orchestration, not record destruction. Unnecessary destructive capability increases blast radius: any downstream skill, prompt injection, or misuse of the CLI can permanently remove CRM records and associated notes, creating integrity and availability risk for user data.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding

The default command uses uv tool run fulcra-api without pinning a specific version. This creates a supply-chain risk: a future malicious or compromised release of the tool could be fetched or resolved and then executed by the concierge flow with access to user data and credentials.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/lib/concierge_cli.py (reported line 46)May include surrounding context.

python
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: