Back to skill

Security audit

Lose It Nutrition

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent and disclosed, but it handles Lose It credentials and health data and has a real redirect-hardening gap that users should review before installing.

Install only if you are comfortable giving this CLI access to your Lose It account and nutrition export. Prefer LOSEIT_EMAIL/LOSEIT_PASSWORD over a plaintext config file on shared machines, keep the token path private and out of backups, and consider waiting for redirect validation on credential and cookie requests before using it with your primary account.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
internal/export/login.go:32
Finding

Credential-Bearing Login Request Can Be Replayed to an Untrusted Host Through HTTP Redirects

Content
View full analysis

Vulnerability Details

File Location: internal/export/login.go:32-53
Vulnerability Type: Redirect-based credential disclosure
Risk Level: Medium

Vulnerable Code

go
// Never POST the email/password to anything but Lose It's own domain.
if err := assertFirstPartyURL(cfg.LoginURL); err != nil {
    return "", err
}

form := url.Values{
    "username":   {cfg.Email},
    "password":   {cfg.Password},
    "grant_type": {"password"},
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, cfg.LoginURL,
    strings.NewReader(form.Encode()))
if err != nil {
    return "", newErr("building login request: %v", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", userAgent)

client := &http.Client{Timeout: 60 * time.Second}
resp, err := client.Do(req)

Technical Analysis

The code validates only the initial login URL with assertFirstPartyURL. It then uses a standard Go http.Client without a CheckRedirect callback. By default, this client follows HTTP redirects.

For HTTP 307 Temporary Redirect and 308 Permanent Redirect responses, Go can preserve the POST method and replay the request body at the redirect destination. Because the request body contains the user's Lose It email and password, a redirect to a non-Lose It host could disclose those credentials. The first-party URL assertion is not applied to each redirect target.

The compiled-in endpoint and initial HTTPS/hostname validation substantially limit exposure, but they do not enforce the documented guarantee across the complete redirect chain.

Attack Path

  1. A user runs loseit-cli login or runs loseit-cli days when automatic authentication is required.
  2. The application validates the compiled-in initial login endpoint and submits a form containing the user's email and password.
  3. A compromised or malicious first-party login endpoint, reverse p ...[truncated 918 chars]
Remediation
View remediation

Remediation Suggestions

Configure the HTTP client with a CheckRedirect callback that validates every redirect destination before the redirected request is sent:

go
client := &http.Client{
    Timeout: 60 * time.Second,
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        if err := assertFirstPartyURL(req.URL.String()); err != nil {
            return err
        }
        return nil
    },
}

For stronger hardening:

  1. Disable automatic redirects for the credential-bearing login request and reject all 3xx responses unless a redirect is explicitly required.
  2. If redirects must be supported, allow only HTTPS destinations on an exact, minimal hostname allowlist rather than every subdomain.
  3. Reject cross-origin redirects and place a conservative limit on redirect depth.
  4. Ensure credentials are never copied into a redirected request unless the destination has passed the same validation as the original URL.
  5. Apply equivalent redirect validation to session-cookie-bearing export requests to preserve the intended destination restrictions consistently.
  6. Add regression tests in which an approved test endpoint returns a 307 or 308 redirect to a second server. Verify that the second server receives neither credentials nor session cookies.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (29)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is about extracting Lose It nutrition data, handling authentication/session tokens, and producing JSON output. The supplied code chunk does none of that. Instead, it implements a command-line completion feature using Cobra to emit shell completion scripts. That is a materially different purpose and an undeclared capability unrelated to the stated read-only nutrition extraction behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for an end-user CLI that logs into Lose It, retrieves export data, parses nutrition CSVs, and emits JSON. The supplied code does none of that. Instead, it is a test module focused on documentation compliance: it walks to the repo root, reads markdown/doc files, checks for banned phrases, ensures disclosure of token-cache/security wording, and detects secret-shaped literals. This is a materially different primary purpose and behavior from the declared extractor functionality. While such tests may belong in the same repository as supporting development infrastructure, this specific code chunk does not implement the declared skill behavior; therefore the description does not accurately represent what this supplied code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description centers on a self-contained Lose It! nutrition extractor with login/export/cookie auth flows and per-day nutrition JSON output. The supplied code chunk does something materially different: it defines a CLI command for reporting application version information. While a version command could exist as a minor auxiliary feature in a larger tool, this specific chunk does not reflect the declared purpose at all and instead exposes an unrelated capability. Therefore, for this chunk, the description does not accurately represent the actual behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a self-contained Lose It! data extractor. The supplied code does not perform any account access, token refresh, export retrieval, nutrition parsing, JSON output, or file writing. Instead, it defines a version package that stores and reports build metadata such as version, commit, build date, and Go runtime version. This is materially different from the declared primary purpose, so the description does not accurately represent this code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a self-contained, read-only Lose It! data extractor. The supplied code instead implements a repository maintenance script for syncing skill package trees across multiple harnesses. It uses argparse, git subprocesses, file comparisons, regex normalization, and can copy or delete files under .claude/.cursor/.codex/skills. It does not log into Lose It!, parse exports, emit nutrition JSON, manage session tokens, or interact with Lose It! data at all. Its primary purpose, accessed resources, side effects, and runtime characteristics are materially different from the declaration.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a self-contained, read-only Lose It data extractor. The supplied code does something entirely different: it is a Python test script for a git-based skill synchronization tool. It creates temporary repositories, copies scripts, edits and deletes skill files, runs git commands, and checks synchronization/fix semantics across harness directories. There is no Lose It login, token handling, ZIP ingestion, cookie use, nutrition data extraction, or JSON output. The code also materially contradicts the claimed implementation constraints by being Python rather than a single static binary. This is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The instruction to treat broad phrases like "can you...", "I want to...", and "help me..." as authorization to perform work weakens consent and scope validation. In an agent skill, this can cause the agent to over-interpret casual language as approval for sensitive actions, increasing the risk of unauthorized operations or data handling beyond the user's intended request.

Content

No source excerpt is available for this finding.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
70% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · README.md (reported line 121)May include surrounding context.

md
This tool handles two **sensitive** local files. Treat both as you would any password:

- **`config.json`** holds your Lose It **email and password in plaintext**. It is **gitignored** —
  never commit it, and don't drop it in a shared, world-readable, or backed-up location. On a shared
  machine, prefer the `LOSEIT_EMAIL` / `LOSEIT_PASSWORD` environment variables over a file on disk.
- **The session-token cache** (`token_path`, default `~/.config/loseit/token`) holds the `liauth`
  cookie — a **reusable, ~14-day session credential** for your account. It is written **owner-only

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
94% confidence
Finding

The skill intentionally persists a reusable session token on disk, and the documentation states it remains valid for about 14 days. Even with 0600 permissions, storing a live bearer-style credential increases the blast radius of local compromise, backup leakage, or accidental exposure, especially because the token can be reused to access the user's nutrition export without re-entering the password. The skill context makes this more sensitive because it handles health-related personal data and a long-lived authenticated session.

Content

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

md
data and store whatever you care about. The only file it writes locally is its own session-token cache
(`token_path`, mode 0600). Single static binary — **no Python or other runtime**.

> ⚠️ **Read-only** for your Lose It data — it reads the export and prints JSON. It is not a no-write
> tool, though: it caches its `liauth` **session token** (a reusable ~14-day credential) to
> `token_path` (mode 0600), and it reads your **plaintext** email/password from `config.json`. Both
> are secrets — see **Security & secrets** below.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
84% confidence
Finding

The documented default behavior permits plaintext credential storage in config.json, with safety relying on user discipline rather than a secure default mechanism. Even though the file warns against shared or backed-up locations, storing account passwords unencrypted on disk is risky because many real environments sync home directories, create backups, or expose working directories more broadly than intended. In this skill's context, that can expose both account access and sensitive nutrition/health data.

Content

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

md
This skill handles two **sensitive** local files. Treat both as you would any password:

- **`config.json`** holds your Lose It **email and password in plaintext**. It is **gitignored** —
  never commit it, and don't place it in a shared, world-readable, or backed-up directory. On a shared
  machine, prefer the `LOSEIT_EMAIL` / `LOSEIT_PASSWORD` environment variables over a file on disk.
- **The session-token cache** (`token_path`, default `~/.config/loseit/token`) holds the `liauth`
  cookie — a **reusable, ~14-day session credential**. It is written **owner-only (`0600`)** in an

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · README.md (reported line 153)May include surrounding context.

md
### The flow (verified 2026-06-18)

1. **Login:** `POST https://api.loseit.com/account/login`, `Content-Type: application/x-www-form-urlencoded`. Form fields: `username` (the Lose It email), `password` (the Lose It password), and `grant_type` (the OAuth literal `password`). Written as prose, not a key=value example, so no secret-shaped literal appears in docs — see CLAWHUB_STANDARDS §3a.
   - It's an OAuth **password-grant** endpoint. **The reCAPTCHA the web login form attaches (`captcha_token`/`captcha_site_key`) is NOT required by the API** — a plain POST authenticates. (Confirmed: bogus creds with *no* captcha return `{"error":"invalid_grant"}`, i.e. a credential error, not a captcha rejection.)
   - On success (HTTP 200) the response sets cookies: **`liauth`** and `fn_auth` (same value), plus `fn_authed=1`. `liauth` is an **ES384 JWT** — `Domain=loseit.com`, `HttpOnly`, **~14-day expiry** (`Max-Age=1209600`; JWT `exp`−`iat`).
2. **Extract:** `GET https://www.loseit.com/export/data` sending the `liauth` and `fn_auth` cookies (both set to the session token) → returns a **ZIP of CSVs** (the user's full export).

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · docs/MACHINE_CONTRACT.md (reported line 98)May include surrounding context.

md
### The flow (verified 2026-06-18)

1. **Login:** `POST https://api.loseit.com/account/login`, `Content-Type: application/x-www-form-urlencoded`. Form fields: `username` (the Lose It email), `password` (the Lose It password), and `grant_type` (the OAuth literal `password`). Written as prose, not a key=value example, so no secret-shaped literal appears in docs — see CLAWHUB_STANDARDS §3a.
   - It's an OAuth **password-grant** endpoint. **The reCAPTCHA the web login form attaches (`captcha_token`/`captcha_site_key`) is NOT required by the API** — a plain POST authenticates. (Confirmed: bogus creds with *no* captcha return `{"error":"invalid_grant"}`, i.e. a credential error, not a captcha rejection.)
   - On success (HTTP 200) the response sets cookies: **`liauth`** and `fn_auth` (same value), plus `fn_authed=1`. `liauth` is an **ES384 JWT** — `Domain=loseit.com`, `HttpOnly`, **~14-day expiry** (`Max-Age=1209600`; JWT `exp`−`iat`).
2. **Extract:** `GET https://www.loseit.com/export/data` sending the `liauth` and `fn_auth` cookies (both set to the session token) → returns a **ZIP of CSVs** (the user's full export).

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · docs/MACHINE_CONTRACT.md (reported line 118)May include surrounding context.

md
### The flow (verified 2026-06-18)

1. **Login:** `POST https://api.loseit.com/account/login`, `Content-Type: application/x-www-form-urlencoded`. Form fields: `username` (the Lose It email), `password` (the Lose It password), and `grant_type` (the OAuth literal `password`). Written as prose, not a key=value example, so no secret-shaped literal appears in docs — see CLAWHUB_STANDARDS §3a.
   - It's an OAuth **password-grant** endpoint. **The reCAPTCHA the web login form attaches (`captcha_token`/`captcha_site_key`) is NOT required by the API** — a plain POST authenticates. (Confirmed: bogus creds with *no* captcha return `{"error":"invalid_grant"}`, i.e. a credential error, not a captcha rejection.)
   - On success (HTTP 200) the response sets cookies: **`liauth`** and `fn_auth` (same value), plus `fn_authed=1`. `liauth` is an **ES384 JWT** — `Domain=loseit.com`, `HttpOnly`, **~14-day expiry** (`Max-Age=1209600`; JWT `exp`−`iat`).
2. **Extract:** `GET https://www.loseit.com/export/data` sending the `liauth` and `fn_auth` cookies (both set to the session token) → returns a **ZIP of CSVs** (the user's full export).

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · docs/PROJECT_INSTRUCTIONS.md (reported line 19)May include surrounding context.

md
### The flow (verified 2026-06-18)

1. **Login:** `POST https://api.loseit.com/account/login`, `Content-Type: application/x-www-form-urlencoded`. Form fields: `username` (the Lose It email), `password` (the Lose It password), and `grant_type` (the OAuth literal `password`). Written as prose, not a key=value example, so no secret-shaped literal appears in docs — see CLAWHUB_STANDARDS §3a.
   - It's an OAuth **password-grant** endpoint. **The reCAPTCHA the web login form attaches (`captcha_token`/`captcha_site_key`) is NOT required by the API** — a plain POST authenticates. (Confirmed: bogus creds with *no* captcha return `{"error":"invalid_grant"}`, i.e. a credential error, not a captcha rejection.)
   - On success (HTTP 200) the response sets cookies: **`liauth`** and `fn_auth` (same value), plus `fn_authed=1`. `liauth` is an **ES384 JWT** — `Domain=loseit.com`, `HttpOnly`, **~14-day expiry** (`Max-Age=1209600`; JWT `exp`−`iat`).
2. **Extract:** `GET https://www.loseit.com/export/data` sending the `liauth` and `fn_auth` cookies (both set to the session token) → returns a **ZIP of CSVs** (the user's full export).

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · internal/config/config.go (reported line 52)May include surrounding context.

go
### The flow (verified 2026-06-18)

1. **Login:** `POST https://api.loseit.com/account/login`, `Content-Type: application/x-www-form-urlencoded`. Form fields: `username` (the Lose It email), `password` (the Lose It password), and `grant_type` (the OAuth literal `password`). Written as prose, not a key=value example, so no secret-shaped literal appears in docs — see CLAWHUB_STANDARDS §3a.
   - It's an OAuth **password-grant** endpoint. **The reCAPTCHA the web login form attaches (`captcha_token`/`captcha_site_key`) is NOT required by the API** — a plain POST authenticates. (Confirmed: bogus creds with *no* captcha return `{"error":"invalid_grant"}`, i.e. a credential error, not a captcha rejection.)
   - On success (HTTP 200) the response sets cookies: **`liauth`** and `fn_auth` (same value), plus `fn_authed=1`. `liauth` is an **ES384 JWT** — `Domain=loseit.com`, `HttpOnly`, **~14-day expiry** (`Max-Age=1209600`; JWT `exp`−`iat`).
2. **Extract:** `GET https://www.loseit.com/export/data` sending the `liauth` and `fn_auth` cookies (both set to the session token) → returns a **ZIP of CSVs** (the user's full export).

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · scripts/sync-harness-skills.py (reported line 22)May include surrounding context.

python
SUBTREE = "skills"

# A reference to something INSIDE the harness's own skills tree, in either slash flavor — the
# skills are written on Windows and quote both `.claude/skills/verify-openloop/...` and
# `pwsh <repo>\.claude\skills\run-e2e\...`. A deeper path is required so that prose enumerating
# the three trees ("`.claude/skills/`, `.cursor/skills/`, `.codex/skills/`", which harness-sync's
# own SKILL.md does) is left alone: that is a list of all three, not a pointer at one.

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/sync-harness-skills.py (reported line 65)May include surrounding context.

python
since a new skill written into one harness is exactly what needs propagating.
    """
    top = f".{harness}/{SUBTREE}"
    out = subprocess.run(["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", top],
                         cwd=ROOT, check=True, capture_output=True, text=True).stdout
    return {p[len(top) + 1:]: ROOT / p for p in out.split("\0") if p}

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/sync-harness-skills.py (reported line 83)May include surrounding context.

python
three harnesses (a brand-new skill and the copies a previous --fix wrote look identical to it),
    and the caller reports that case rather than picking a winner from no evidence.
    """
    default = subprocess.run(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], cwd=ROOT,
                             capture_output=True, text=True).stdout.strip()
    base = subprocess.run(["git", "merge-base", default or "origin/main", "HEAD"], cwd=ROOT,
                          capture_output=True, text=True).stdout.strip()

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/sync-harness-skills.py (reported line 85)May include surrounding context.

python
"""
    default = subprocess.run(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], cwd=ROOT,
                             capture_output=True, text=True).stdout.strip()
    base = subprocess.run(["git", "merge-base", default or "origin/main", "HEAD"], cwd=ROOT,
                          capture_output=True, text=True).stdout.strip()
    paths = [f".{h}/{SUBTREE}/{rel}" for h in HARNESSES for rel in rel_paths]
    changed = set()

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/sync-harness-skills.py (reported line 94)May include surrounding context.

python
cmds = ([["git", "diff", "--name-only", base, "--", *paths]] if base else []) + \
           [["git", "status", "--porcelain", "--", *paths]]
    for cmd in cmds:
        out = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True).stdout
        for line in out.splitlines():
            line = line[3:] if cmd[1] == "status" else line          # strip porcelain XY prefix
            for h in HARNESSES:

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · scripts/test-sync-harness-skills.py (reported line 38)May include surrounding context.

python
SCRIPT = Path(__file__).resolve().parent / "sync-harness-skills.py"
HARNESSES = ("claude", "cursor", "codex")
SKILL = "skills/demo/SKILL.md"


def build_repo(tmp):

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/test-sync-harness-skills.py (reported line 43)May include surrounding context.

python
def build_repo(tmp):
    """A minimal repo: three harness trees holding one identical skill, all committed."""
    subprocess.run(["git", "init", "-q"], cwd=tmp, check=True)
    (tmp / "scripts").mkdir()
    shutil.copyfile(SCRIPT, tmp / "scripts" / SCRIPT.name)
    for h in HARNESSES:

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/test-sync-harness-skills.py (reported line 54)May include surrounding context.

python
(tmp / ".gitignore").write_text(".claude/worktrees/\n", encoding="utf-8")
    (tmp / ".claude" / "worktrees").mkdir(parents=True)
    (tmp / ".claude" / "worktrees" / "junk.md").write_text("ignore me\n", encoding="utf-8")
    subprocess.run(["git", "add", "-A"], cwd=tmp, check=True)
    subprocess.run(["git", "-c", "user.email=t@t", "-c", "user.name=t",
                    "commit", "-qm", "init"], cwd=tmp, check=True)

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/test-sync-harness-skills.py (reported line 96)May include surrounding context.

python
(tmp / ".gitignore").write_text(".claude/worktrees/\n", encoding="utf-8")
    (tmp / ".claude" / "worktrees").mkdir(parents=True)
    (tmp / ".claude" / "worktrees" / "junk.md").write_text("ignore me\n", encoding="utf-8")
    subprocess.run(["git", "add", "-A"], cwd=tmp, check=True)
    subprocess.run(["git", "-c", "user.email=t@t", "-c", "user.name=t",
                    "commit", "-qm", "init"], cwd=tmp, check=True)

Static analysis

No suspicious patterns detected.