Back to skill

Security audit

Ward Public Repo Git Gate

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent public-repository safety checker, but its script can miss committed sensitive content and can print credential-bearing Git remotes into logs.

Install only if you treat it as an advisory checklist, not a complete public-release or PR safety gate. Avoid running `--check-remote` on repositories with credential-bearing remotes unless the script is fixed to redact URLs, and do not rely on `--changed-since` to prove committed branch contents are safe until it scans `HEAD` content directly.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/public_repo_check.py:58
Finding
Changed-since scans inspect mutable working-tree content instead of committed HEAD content## Vulnerability Details **File Location**: `scripts/public_repo_check.py`, lines 58-68 and 136-141 **Vulnerability Type**: Security-gate bypass caused by scanning the wrong Git content source **Risk Level**: High ### Vulnerable Code ```python def read_content(repo: Path, path: str, staged: bool) -> bytes: if staged: result = subprocess.run( ["git", "-C", str(repo), "show", f":{path}"], check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if result.returncode: return b"" return result.stdout return (repo / path).read_bytes() ``` The affected function is invoked by the changed-since scanning path as follows: ```python paths = git_paths(repo, staged, changed_since) print(f"branch: {branch or '(detached)'}") scope = "staged" if staged else (f"changed since {changed_since}" if changed_since else "tracked/untracked") print(f"checking {len(paths)} {scope} candidate paths") findings.extend(scan_paths(repo, paths, staged, config)) if check_remote_flag: findings.extend(check_remote(repo)) ``` ### Technical Analysis When `--changed-since REF` is used, `git_paths()` obtains candidate filenames from the committed range `REF...HEAD`. However, `scan_paths()` receives only the `staged` Boolean to select the content source. Because a changed-since scan is not a staged scan, `staged` is false and `read_content()` reads each candidate from the mutable working-tree filesystem: ```python return (repo / path).read_bytes() ``` Consequently, the set of paths represents committed `HEAD` changes, but the bytes being inspected may represent unrelated, uncommitted working-tree changes. The public push gate therefore does not reliably inspect the exact content that Git will push. This is a time-of-check and content-source mismatch. A sensitive committed blob can be hidden from the scanner by replacin ...[truncated 1336 chars]
Remediation
## Remediation Suggestions Make the content source explicit rather than deriving it solely from the `staged` flag: - For `--staged`, read index content with `git show :path`. - For `--changed-since`, read committed content with `git show HEAD:path`. - For `--all`, read working-tree files where that behavior is intentionally required. - Treat a failure to read a candidate from the selected Git tree as a gate failure rather than returning empty bytes. - Consider using `git diff` with appropriate options to scan added content directly where practical. - Add regression tests in which the committed `HEAD` version contains a known secret while the working-tree version is benign. The changed-since gate must reject that repository. - Add tests for deleted, renamed, conflicted, and submodule paths so all Git object states are handled explicitly. A suitable design is to pass a content-source enum such as `INDEX`, `HEAD`, or `WORKTREE` into `scan_paths()` and `read_content()`, preventing future ambiguity between scan scope and content source.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/public_repo_check.py:111
Finding
Credential-bearing Git remote URLs are printed without redaction## Vulnerability Details **File Location**: `scripts/public_repo_check.py`, lines 111-120 **Vulnerability Type**: Plaintext credential disclosure through process output and logs **Risk Level**: Medium ### Vulnerable Code ```python def check_remote(repo: Path) -> list[str]: findings: list[str] = [] remotes = git(repo, "remote", check=False).split() for remote in remotes: url = git(repo, "remote", "get-url", remote, check=False).strip() if re.search(r"https?://[^/\s:]+:[^@\s]+@", url): findings.append(f"remote {remote}: URL contains embedded credentials") print(f"remote {remote}: {url}") if not remotes: findings.append("repository has no configured remote") return findings ``` ### Technical Analysis The function correctly detects HTTP or HTTPS remote URLs containing user information and credentials. Nevertheless, it prints the original URL unconditionally: ```python print(f"remote {remote}: {url}") ``` Detection therefore does not prevent disclosure. A URL such as `https://username:token@example.com/repository.git` is emitted in full to standard output. The documented workflow recommends invoking the script with `--check-remote`. In automated agents and CI systems, standard output is commonly retained in build logs, conversation transcripts, observability systems, or support artifacts. Those records can have a broader readership and a longer retention period than the local Git configuration. ### Attack Path 1. A repository has a remote containing embedded credentials, for example: ```text https://username:access-token@example.com/organization/repository.git ``` 2. A user or automation system runs the gate with `--check-remote`. 3. `check_remote()` recognizes that the URL contains credentials and records a finding. 4. The function still prints the complete, unredacted URL to standard output. 5. CI, Agent, terminal-sessi ...[truncated 769 chars]
Remediation
## Remediation Suggestions Never print the original remote URL when it may contain user information: - Parse and sanitize the URL before producing output. - Remove the entire user-information component or replace it with a fixed marker such as `***@`. - Prefer reporting only the remote name, transport scheme, host, and sanitized repository path. - Apply redaction before both normal output and error reporting. - Account for HTTP, HTTPS, percent-encoded credentials, and other URL forms supported by Git. - Consider avoiding URL output entirely; reporting that a named remote has embedded credentials is sufficient for this gate. - Add tests asserting that known passwords and tokens never appear in either standard output or standard error. - If unredacted URLs have already entered CI or Agent logs, remove affected logs where possible and rotate the exposed credentials. For example, transform: ```text https://username:token@example.com/repository.git ``` into: ```text https://***@example.com/repository.git ``` before any output operation.
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • 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
Findings (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill claims to provide commit, push, and PR safety gates covering PR base/head checks, CI, merge prerequisites, and personal-data review, but the described implementation does not actually verify several of those controls. Users may rely on the skill as a complete public-repo gate and proceed with commits, pushes, or PRs under a false sense of safety, allowing secrets, internal material, misbased PRs, or unverified changes to be published.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs the agent to read repository state and execute shell commands, but it does not declare any explicit tool scope or allowed-tools boundary. That creates an authorization gap where an agent runtime may permit broader file or shell access than the skill's stated purpose, increasing the chance of unintended repository inspection or command execution outside the intended gate workflow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def git(repo: Path, *args: str, check: bool = True) -> str:
    result = subprocess.run(
        ["git", "-C", str(repo), *args],
        check=False,
        stdout=subprocess.PIPE,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def read_content(repo: Path, path: str, staged: bool) -> bytes:
    if staged:
        result = subprocess.run(
            ["git", "-C", str(repo), "show", f":{path}"],
            check=False,
            stdout=subprocess.PIPE,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
findings.append(f"current branch {branch} is protected; use a feature branch and PR")

    if staged and not changed_since:
        diff_check = subprocess.run(
            ["git", "-C", str(repo), "diff", "--cached", "--check"],
            check=False,
            stdout=subprocess.PIPE,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.