Back to skill

Security audit

Evening Debrief

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does the advertised evening debrief, but it ships under-scoped helpers that can redirect authenticated API traffic and expose broader Fulcra/Attio authority than the debrief requires.

Review this skill before installing. It is not clearly malicious, but it should only be used in a trusted environment with known-good Fulcra tooling, no untrusted `FULCRA_API_BASE`, `ATTIO_API_BASE`, or `FULCRA_CLI_COMMAND` settings, and an understanding that the package includes broader shared CRM/Fulcra helpers than the evening debrief itself needs.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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/lib/fulcra_annotations.py:24
Finding

Configurable Fulcra API Endpoint Can Exfiltrate the Access Token and Personal Data

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 if token: return token fail("No Fulcra token available. Set FULCRA_ACCESS_TOKEN or run Fulcra CLI auth login.") 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: body = response.read() return response.status, body.decode() if body else "" ``` ### Technical Analysis The destination receiving the Fulcra bearer token is selected directly from the `FULCRA_API_BASE` environment variable. There is no validation of the URL scheme, hostname, port, or redirect destination. Every request attaches the Fulcra ...[truncated 1802 chars]
Remediation
View remediation

T07 · Tool Hijacking and Spoofing

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

Environment-Controlled CLI Command Enables Local Tool Spoofing and Arbitrary Program Invocation

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

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

Attio API Endpoint Override Can Disclose CRM Credentials

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: body = resp.read().decode() or "{}" return resp.status, json.loads(body) ``` ### Technical Analysis The bundled Attio helper loads `ATTIO_API_KEY` from the environment or persistent local secret storage and sends it to a base URL selected by `ATTIO_API_BASE`. No scheme or destination validation is applied. Although the main evening-debrief execution path does not directly import or call this client, the file is shipped as an executable CLI and shared library within the Skill package. Direct invocation or reuse by another bundled concierge component can therefore expose the CRM token to an attacker-controlled endpoint. The client supports reading people and company records, adding notes, updating records, and deleting records. A stolen token may therefore provide access significantly beyond the evening-debrief feature's declared Fulcra-only purpose. ### Attack Path 1. An attacker influences the environment of a process that invokes the bundled Attio client. 2. The attacker sets `ATTIO_API_BASE` to a ...[truncated 866 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/lib/concierge_secrets.py:46
Finding

Secret Temporary File Is Permission-Hardened Only After Sensitive Data Is Written

Content
View full analysis
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() 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) except OSError: pass os.replace(tmp, SECRETS_PATH) ``` ### Technical Analysis The comment states that permissions are restricted before writing the value, but the implementation calls `chmod(0o600)` only after `json.dump()` and file closure. When `open(tmp, "w")` creates a new file, its initial permissions depend on the process umask. Under a permissive umask, another local user may be able to read the temporary file during the interval between creation and `chmod`. The file contains all existing stored secrets plus the newly supplied value in plaintext. The fixed temporary filename, `secrets.tmp`, also allows concurrent writers to interfere with one another and creates avoidable race conditions. The code ignores `chmod` failures, so the final replacement can proceed even if secure permissions were not applied. ### Attack Path 1. A multi-user system runs a setup helper that calls `set_secret()`. 2. The process has a permissive umask or the secrets directory has overly broad traversal permissions. 3. The predictable `secrets.tmp` file is created and populated before its mode is changed. 4. A local attacker monitoring the directory opens the file during this interval. 5. The attacker r ...[truncated 651 chars]
Remediation
View remediation

other

Note
Location
scripts/lib/concierge_fulcra.py:109
Finding

Failed Read-Back Verification Is Reported as a Successful Save

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (35)

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

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The client builds outbound requests using API_BASE from the ATTIO_API_BASE environment variable and automatically attaches the Bearer token to every request. If an attacker can influence that environment variable, the code will send the Attio API key to an arbitrary host, causing credential exfiltration and enabling unauthorized CRM access. In the context of an end-of-day debrief skill, this is more dangerous because the skill should only annotate Fulcra data, yet this shared client can silently redirect privileged traffic elsewhere.

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 taken from the environment and concatenated into outbound requests that include the Bearer token from access_token(). If an attacker can set FULCRA_API_BASE, the script will send authenticated requests and sensitive annotation data to an attacker-controlled server, resulting in credential exfiltration and full misuse of the user's Fulcra access under this agent skill.

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

If the implementation only reads annotation definitions/history and exposes generic CLI read commands, then the skill does not do what it claims and may cause silent failures or misleading user trust. While primarily an integrity and transparency issue, deceptive or undeclared data access still matters because users may reveal sensitive reflections expecting a save workflow that never occurs.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the implementation only reads annotation definitions/history and exposes generic CLI read commands, then the skill does not do what it claims and may cause silent failures or misleading user trust. While primarily an integrity and transparency issue, deceptive or undeclared data access still matters because users may reveal sensitive reflections expecting a save workflow that never occurs.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the implementation only reads annotation definitions/history and exposes generic CLI read commands, then the skill does not do what it claims and may cause silent failures or misleading user trust. While primarily an integrity and transparency issue, deceptive or undeclared data access still matters because users may reveal sensitive reflections expecting a save workflow that never occurs.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the implementation only reads annotation definitions/history and exposes generic CLI read commands, then the skill does not do what it claims and may cause silent failures or misleading user trust. While primarily an integrity and transparency issue, deceptive or undeclared data access still matters because users may reveal sensitive reflections expecting a save workflow that never occurs.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the implementation only reads annotation definitions/history and exposes generic CLI read commands, then the skill does not do what it claims and may cause silent failures or misleading user trust. While primarily an integrity and transparency issue, deceptive or undeclared data access still matters because users may reveal sensitive reflections expecting a save workflow that never occurs.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the implementation only reads annotation definitions/history and exposes generic CLI read commands, then the skill does not do what it claims and may cause silent failures or misleading user trust. While primarily an integrity and transparency issue, deceptive or undeclared data access still matters because users may reveal sensitive reflections expecting a save workflow that never occurs.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
93% confidence
Finding

This library exposes broad record creation, update, deletion, and note-writing operations for Attio, far beyond what an evening debrief skill needs. Excess capability increases blast radius: if the skill, another caller, or prompt-driven tooling misuses this client, it can modify or delete arbitrary CRM records rather than only storing a debrief annotation. Because the declared skill scope is narrow, this mismatch makes the design more dangerous, not less.

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 30)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

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding

The README instructs users to run uv tool run fulcra-api without pinning a specific version or source, which can cause execution of an unexpected or newly published package version. In a tool-install-and-run workflow, that creates a supply-chain risk if the package is compromised, typosquatted, or changed in a breaking or unsafe way.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The activation phrase guidance includes broad natural-language examples like 'how was my day' and 'let's debrief,' which can overlap with ordinary conversation and trigger the skill unintentionally. Because this skill reads calendar context and writes a structured Fulcra annotation, accidental activation can expose private context to the skill and create unwanted records.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill invokes shell commands, reads and writes files, and depends on networked tooling, but it declares no explicit tool scope or permissions boundary. That makes the effective capability set broader than the user-facing contract, increasing the chance of over-privileged execution or abuse if the referenced scripts or dependencies behave unexpectedly.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The invocation guidance includes broad phrases like 'how was my day' or 'let's debrief,' which can match ordinary conversation and trigger access to calendar and personal check-in data without sufficiently clear user intent. In a privacy-sensitive skill, ambiguous triggering increases the chance of accidental activation and unnecessary data retrieval or storage.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
84% confidence
Finding

The prerequisite uses uv tool run fulcra-api without pinning a version, so the skill may execute whatever version is currently resolved at runtime. This creates supply-chain and integrity risk: a future or tampered release could change behavior, request broader data access, or exfiltrate sensitive calendar/journal data under the guise of the debrief workflow.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding

Using 'uv tool run fulcra-api' without pinning a specific version makes the executed dependency mutable over time, which creates a supply-chain risk. A newer or compromised package version could change behavior or execute malicious code when this skill reads calendars or writes debrief data to Fulcra.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
95% confidence
Finding

The code executes an external command via subprocess, and the executable/argument prefix is derived from the FULCRA_CLI_COMMAND environment variable. Although shell=True is not used, this still allows arbitrary command selection if an attacker can influence the process environment, which is a code-execution primitive in the trust boundary of the agent runtime.

Content

Scanner excerpt · scripts/evening_debrief.py (reported line 62)May include surrounding context.

python
def fulcra_cli(args: list[str]) -> tuple[int, str]:
    base = os.environ.get("FULCRA_CLI_COMMAND", "uv tool run fulcra-api")
    try:
        proc = subprocess.run([*shlex.split(base), *args], capture_output=True, text=True, timeout=90)
    except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
        return 1, f"{type(exc).__name__}: {exc}"
    return proc.returncode, proc.stdout if proc.returncode == 0 else proc.stdout + proc.stderr

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

Medium
Category
Data Flow
Confidence
99% confidence
Finding

There is a tainted flow from an environment variable into subprocess.run, enabling command execution controlled by runtime environment configuration. In an agent/skill context, environment variables are often part of deployment configuration and may be modifiable by adjacent tooling, wrappers, or compromised launch contexts, making this especially risky because the skill routinely handles personal calendar and reflection data.

Content

Scanner excerpt · scripts/evening_debrief.py (reported line 62)May include surrounding context.

python
def fulcra_cli(args: list[str]) -> tuple[int, str]:
    base = os.environ.get("FULCRA_CLI_COMMAND", "uv tool run fulcra-api")
    try:
        proc = subprocess.run([*shlex.split(base), *args], capture_output=True, text=True, timeout=90)
    except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
        return 1, f"{type(exc).__name__}: {exc}"
    return proc.returncode, proc.stdout if proc.returncode == 0 else proc.stdout + proc.stderr