Back to skill

Security audit

Wip Repo Permissions Hook

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed repo-visibility guard, but its hook and audit logic can miss important cases, so users should review it before relying on it for protection.

Install only if you are comfortable treating this as an advisory/local guard rather than a complete enforcement boundary. Before relying on it, fix or compensate for the fail-open behavior, valid gh command forms it does not parse, the 200-repository audit limit, and the unpinned MCP dependency; consider server-side GitHub organization controls for critical repository-visibility policy.

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

T09 · Insecure Skill Coding Practices

Error
Location
core.mjs:109
Finding

Repository Visibility Guard Can Be Bypassed with Valid GitHub CLI Syntax

Content
View full analysis

Vulnerability Details

File Location: core.mjs:109-121; enforcement behavior in guard.mjs:46-49
Vulnerability Type: Incomplete security-command parsing
Risk Level: High

Vulnerable Code

js
export function parseVisibilityCommand(command) {
  // Match: gh repo edit <org/repo> ... --visibility public
  const editMatch = command.match(/gh\s+repo\s+edit\s+([^\s]+)/);
  if (!editMatch) return null;

  const visibilityMatch = command.match(/--visibility\s+(public|private|internal)/);
  if (!visibilityMatch || visibilityMatch[1] !== 'public') return null;

  const slug = editMatch[1];
  const parts = slug.split('/');
  if (parts.length !== 2) return null;

  return { org: parts[0], repo: parts[1], isVisibilityChange: true };
}

The hook treats a parsing failure as an irrelevant command:

js
// Only check commands that look like visibility changes
const parsed = parseVisibilityCommand(command);
if (!parsed) {
  process.exit(0);
}

Technical Analysis

The security control attempts to recognize visibility changes by applying regular expressions to an unparsed shell command. It only recognizes a narrow form resembling:

bash
gh repo edit owner/repository --visibility public

The expression for the visibility option requires whitespace between --visibility and public. GitHub CLI accepts the conventional equals-sign option form:

bash
gh repo edit owner/repository --visibility=public

This form does not match /--visibility\s+(public|private|internal)/, causing parseVisibilityCommand() to return null. The hook then exits successfully and does not return a denial.

The parser also assumes that the first token after gh repo edit is an explicit owner/repository slug. Valid forms that operate on the repository in the current working directory, or other syntactically different invocations, are not reliably covered. Raw regular-expression ...[truncated 1460 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not use regular expressions over raw shell text as the primary security boundary.
  2. Prefer intercepting a structured tool invocation where the executable and argument array are provided separately.
  3. If Bash commands must be inspected, use a maintained shell parser and recursively inspect every command in pipelines, command substitutions, groups, and compound statements.
  4. Explicitly support both --visibility public and --visibility=public, arbitrary valid option ordering, quoted arguments, and invocations that infer the repository from the current working directory.
  5. Resolve implicit repository targets before allowing a public visibility operation.
  6. Conservatively deny any gh repo edit command containing an ambiguous or unparseable visibility option.
  7. Add regression tests for equivalent CLI forms, shell composition, aliases, quoting, omitted repository arguments, and malformed commands.
  8. Where possible, enforce the policy using GitHub organization rules or another server-side control, rather than relying exclusively on a client-side command hook.

T09 · Insecure Skill Coding Practices

Warning
Location
guard.mjs:22
Finding

Security Hook Fails Open on Malformed Input and Runtime Errors

Content
View full analysis

Vulnerability Details

File Location: guard.mjs:22-34 and guard.mjs:64
Vulnerability Type: Fail-open security control
Risk Level: Medium

Vulnerable Code

js
async function main() {
  let raw = '';
  for await (const chunk of process.stdin) {
    raw += chunk;
  }

  let input;
  try {
    input = JSON.parse(raw);
  } catch {
    // Can't parse input, allow by default
    process.exit(0);
  }

The top-level error handler also allows execution after any uncaught failure:

js
main().catch(() => process.exit(0));

Technical Analysis

This hook acts as a security gate for operations that could disclose an entire repository. Nevertheless, malformed JSON and every unexpected runtime exception result in a successful process exit without a deny response.

This is a fail-open design: when the control cannot determine whether an operation is safe, it removes itself from the decision path. Fail-open behavior is unsuitable for a preventive disclosure control because input-format changes, corrupted hook messages, unexpected value types, or implementation defects can silently disable enforcement.

For example, tool_input.command is used without type validation. If an unexpected non-string value reaches parseVisibilityCommand(), calling command.match() can throw. The top-level catch then exits successfully instead of denying the operation.

Attack Path

  1. A Bash request reaches the hook with malformed JSON, an incompatible payload shape, or an unexpected non-string command value.
  2. JSON parsing fails or downstream command processing throws an exception.
  3. The local catch block or main().catch(...) handles the failure.
  4. The handler exits with status zero and emits no denial decision.
  5. If the host interprets this successful exit as approval, the underlying Bash command proceeds without repository-visibility validation.
  6. A visibility-changing command m ...[truncated 614 chars]
Remediation
View remediation

Remediation Suggestions

  1. Fail closed when a Bash hook request cannot be parsed or safely classified.
  2. Return an explicit deny decision with a sanitized explanation instead of exiting silently.
  3. Validate the complete input schema before use, including requiring tool_name and tool_input.command to have expected types.
  4. Distinguish irrelevant non-Bash requests from malformed Bash requests; only the former should be allowed without inspection.
  5. Replace the blanket top-level catch with explicit error handling and secure default behavior.
  6. Record sanitized diagnostics through an appropriate logging channel so failures can be investigated without contaminating protocol output.
  7. Add tests for empty input, malformed JSON, missing properties, non-string command values, oversized input, dependency failures, and GitHub CLI timeouts.
  8. Confirm and document how the host interprets exit status and hook output, ensuring every failure mode reliably prevents execution.

T09 · Insecure Skill Coding Practices

Warning
Location
core.mjs:73
Finding

Organization Audit Silently Omits Public Repositories Beyond the 200-Repository Limit

Content
View full analysis

Vulnerability Details

File Location: core.mjs:73-85
Vulnerability Type: Incomplete security audit coverage
Risk Level: Medium

Vulnerable Code

js
export function auditOrg(org) {
  // Get all public repos
  let repos;
  try {
    const json = execFileSync('gh', [
      'repo', 'list', org,
      '--visibility', 'public',
      '--json', 'name',
      '--limit', '200',
    ], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000 });
    repos = JSON.parse(json);
  } catch (e) {
    throw new Error(`Failed to list repos for ${org}: ${e.message}`);
  }

Technical Analysis

The CLI and documentation describe this operation as auditing all public repositories in an organization. The implementation, however, requests no more than 200 repositories and does not implement pagination, check whether additional results exist, or warn that the audit may be incomplete.

The resulting repository array is treated as the complete population. If every returned entry passes, the CLI can report that all public repositories have private counterparts even when unchecked repositories exist beyond the result limit.

This is a completeness failure in a security-audit function. The result can create false assurance and prevent missing private counterparts from being detected.

Attack Path

  1. An organization has more than 200 public repositories.
  2. An operator runs:
    bash
    wip-repo-permissions audit organization
    
  3. gh repo list returns at most 200 entries because of --limit 200.
  4. The implementation checks only those returned repositories.
  5. Public repositories outside that set are never examined.
  6. If the checked subset is compliant, the CLI reports that all public repositories are compliant.
  7. A repository without a -private counterpart can remain undetected outside the audited subset.

Impact Assessment

This issue provides no direct ...[truncated 509 chars]

Remediation
View remediation

Remediation Suggestions

  1. Implement explicit pagination until no additional GitHub results remain.
  2. Prefer a GitHub API query that exposes pagination metadata and allows completeness to be verified.
  3. Never emit an all-clear result unless the complete public-repository population was successfully enumerated.
  4. If enumeration is truncated, rate-limited, timed out, or otherwise incomplete, return an audit error with a nonzero exit status.
  5. Include the total number of discovered and checked repositories in the output.
  6. Add automated tests using organizations or mocked responses containing more than 200 repositories.
  7. Consider configurable concurrency and retry behavior so larger organizations can be audited reliably without excessive API load.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (4)

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Content

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

Claude Code Setup

Add to ~/.claude/settings.json:

json
{

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Content

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

Claude Code Setup

Add to ~/.claude/settings.json:

json
{

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified with a caret range (^1.0.0), which allows installation of newer compatible releases without review. In a security-sensitive repo-permissions hook, this weakens supply-chain control and can unexpectedly pull in vulnerable or behavior-changing code.

Content

Scanner excerpt · package.json (reported line 29)May include surrounding context.

json
"url": "git+https://github.com/wipcomputer/wip-ai-devops-toolbox.git"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}

Unverifiable Dependency: @modelcontextprotocol/sdk has 3 known advisory(ies) (CVE-2026-25536 (@modelcontextprotocol/sdk has cross-client data leak via shared server/transport); CVE-2026-0621 (Anthropic's MCP TypeScript SDK has a ReDoS vulnerability); CVE-2025-66414 (Model Context Protocol (MCP) TypeScript SDK does not enable DNS rebinding protec)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding

The manifest references @modelcontextprotocol/sdk without an exact version, while multiple advisories exist for this package family. Because the installed version is not fixed, deployments may resolve to an affected release, which is especially concerning for a hook that may process sensitive repository metadata or interact with MCP transports.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.