Back to skill

Security audit

portable-deployment-audit

Security checks for vulnerabilities and agentic risk

Overview

This is a read-only local audit skill whose file scanning is disclosed and purpose-aligned, with one symlink boundary caveat users should understand.

Install only if you want a local read-only audit tool that scans project files, including env and config files, for security indicators. Run it on directories you control, and be careful with untrusted repositories because symlinks may cause the scanner to inspect files outside the selected target.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/audit.cjs:231
Finding

Target Directory Escape Through Symbolic File Links

Content
View full analysis

Vulnerability Details

File Location: scripts/audit.cjs:231-260
Vulnerability Type: Target-boundary bypass through symbolic links
Risk Level: Medium

Vulnerable Code

js
function readUtf8(filePath) {
  try {
    return fs.readFileSync(filePath, 'utf8');
  } catch {
    return null;
  }
}

function getFilesRecursively(dir, extensions = DEFAULT_EXTENSIONS, ignoredDirs = DEFAULT_IGNORED_DIRS) {
  const files = [];

  function walk(currentDir) {
    let entries = [];
    try {
      entries = fs.readdirSync(currentDir, { withFileTypes: true });
    } catch {
      return;
    }

    for (const entry of entries) {
      const fullPath = path.join(currentDir, entry.name);
      if (entry.isDirectory()) {
        if (!ignoredDirs.has(entry.name)) {
          walk(fullPath);
        }
        continue;
      }

      if (extensions.some(ext => entry.name.endsWith(ext))) {
        files.push(fullPath);
      }
    }
  }

  walk(dir);
  return files;
}

Technical Analysis

Recursive discovery does not reject symbolic links or verify that each candidate's canonical path remains beneath the canonical --target directory. A symbolic link is not treated as a directory by entry.isDirectory(), so a file symlink whose name has an accepted extension is added to the scan list. Later, fs.readFileSync() follows the symbolic link and reads its destination.

For example, a repository may contain config.json as a symbolic link to a sensitive JSON file outside the repository. When the repository is audited, the external file is read using the auditor process's privileges. This violates the documented target boundary.

The scanner does not include matched secret values in its report, which limits direct disclosure. Nevertheless, generated findings can reveal the destination file's existence and content characteristics, including whether it appears to contain private keys, credentials, passwords, tokens, or risky configuration.

Attack Path

  1. A ...[truncated 1314 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject symbolic links during recursive discovery by checking entry.isSymbolicLink() before accepting a file.
  • Canonicalize the target root once with fs.realpathSync() or fs.realpathSync.native().
  • Before every file read, canonicalize the candidate path and use path.relative() to verify that it remains beneath the canonical target root.
  • Reject candidates when the relative path is absolute, equals .., or begins with .. followed by a path separator.
  • Perform containment validation immediately before opening the file to reduce time-of-check/time-of-use risk.
  • Prefer opening files without following symlinks where the platform supports an equivalent to O_NOFOLLOW, and then inspect the opened descriptor.
  • Apply the same containment policy to explicitly supplied --env-file and --dockerfile paths unless reading external files is an intentional, clearly documented capability.
  • Add regression tests covering file symlinks to external files, links within the target, broken links, and link replacement during scanning.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (11)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/audit.cjs (reported line 14)May include surrounding context.

js
const DEFAULT_EXTENSIONS = [
  '.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.java', '.kt', '.groovy',
  '.json', '.env', '.yml', '.yaml', '.properties', '.conf', '.ini', '.xml'
];

const DEFAULT_IGNORED_DIRS = new Set([

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/audit.cjs (reported line 194)May include surrounding context.

js
const DEFAULT_EXTENSIONS = [
  '.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.java', '.kt', '.groovy',
  '.json', '.env', '.yml', '.yaml', '.properties', '.conf', '.ini', '.xml'
];

const DEFAULT_IGNORED_DIRS = new Set([

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/audit.cjs (reported line 194)May include surrounding context.

js
function discoverEnvFiles(targetDir, explicitPath) {
  const candidates = new Set();
  if (explicitPath) candidates.add(explicitPath);
  ['.env', '.env.local', '.env.production', '.env.development', path.join('skills', '.env')].forEach(rel => {
    candidates.add(path.join(targetDir, rel));
  });
  return Array.from(candidates).filter(fileExists);

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/audit.cjs (reported line 194)May include surrounding context.

js
function discoverEnvFiles(targetDir, explicitPath) {
  const candidates = new Set();
  if (explicitPath) candidates.add(explicitPath);
  ['.env', '.env.local', '.env.production', '.env.development', path.join('skills', '.env')].forEach(rel => {
    candidates.add(path.join(targetDir, rel));
  });
  return Array.from(candidates).filter(fileExists);

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/audit.cjs (reported line 194)May include surrounding context.

js
function discoverEnvFiles(targetDir, explicitPath) {
  const candidates = new Set();
  if (explicitPath) candidates.add(explicitPath);
  ['.env', '.env.local', '.env.production', '.env.development', path.join('skills', '.env')].forEach(rel => {
    candidates.add(path.join(targetDir, rel));
  });
  return Array.from(candidates).filter(fileExists);

Privileged Container / Container Escape

High
Category
Privilege Escalation
Confidence
80% confidence
Finding

Potential security issue detected. Manual review is recommended.

Content

Scanner excerpt · scripts/audit.cjs (reported line 537)May include surrounding context.

js
if (/:latest\b/i.test(content)) {
      addFinding(runtime, 'MEDIUM', 'DOCKER', 'Dockerfile uses floating :latest tag', { file: rel });
    }
    if (/--privileged/i.test(content)) {
      addFinding(runtime, 'CRITICAL', 'DOCKER', 'Privileged container flag detected', { file: rel });
    }
  }

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding

Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

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 · scripts/audit.cjs (reported line 475)May include surrounding context.

js
}

  const sensitivePatterns = [
    { pattern: /(^|\/)\.env(\.|$)?/, level: 'CRITICAL', message: 'Sensitive env file is world-readable' },
    { pattern: /\.(key|pem)$/i, level: 'CRITICAL', message: 'Key material is world-readable' },
    { pattern: /\.(json|ya?ml|properties)$/i, level: 'HIGH', message: 'Configuration file is world-readable' },
  ];

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 · scripts/audit.cjs (reported line 476)May include surrounding context.

js
}

  const sensitivePatterns = [
    { pattern: /(^|\/)\.env(\.|$)?/, level: 'CRITICAL', message: 'Sensitive env file is world-readable' },
    { pattern: /\.(key|pem)$/i, level: 'CRITICAL', message: 'Key material is world-readable' },
    { pattern: /\.(json|ya?ml|properties)$/i, level: 'HIGH', message: 'Configuration file is world-readable' },
  ];

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 · scripts/audit.cjs (reported line 477)May include surrounding context.

js
}

  const sensitivePatterns = [
    { pattern: /(^|\/)\.env(\.|$)?/, level: 'CRITICAL', message: 'Sensitive env file is world-readable' },
    { pattern: /\.(key|pem)$/i, level: 'CRITICAL', message: 'Key material is world-readable' },
    { pattern: /\.(json|ya?ml|properties)$/i, level: 'HIGH', message: 'Configuration file is world-readable' },
  ];

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 · scripts/audit.cjs (reported line 501)May include surrounding context.

js
}

    if ((mode & 0o001) !== 0 && /\.(js|cjs|mjs|sh)$/i.test(file)) {
      addFinding(runtime, 'LOW', 'PERMISSIONS', 'Executable script is world-executable', {
        file: relativeToTarget(runtime, file),
        mode: mode.toString(8),
      });

Static analysis

No suspicious patterns detected.