All skills
asyrafhussin avatar

/technical-debt

@4df703d

Technical debt inventory, prioritization, and audit for PHP/Laravel (MySQL) and Node/TypeScript/React projects. Use when assessing code health, identifying refactoring candidates, planning debt paydown, or auditing a codebase for accumulated debt. Triggers on "audit technical debt", "find tech debt", "debt inventory", "what should we refactor first", or tasks involving code health, security debt, performance debt, data debt, observability debt, debt prioritization, or remediation planning.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/technical-debt

This session only. Nothing lands on disk.

rulessecurity-input-validation.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Missing Input Validation

Impact: CRITICAL (Foundational defense; injection vectors compound silently across endpoints)

Untrusted input flowing into queries, templates, file paths, or shell commands is the source of most OWASP top-10 vulnerabilities. Every endpoint that accepts external input without explicit validation is debt — and it compounds because each new endpoint adds another opportunity.

How to Detect

# Laravel: controllers reaching directly into request without FormRequest
grep -rEn '\$request->(input|get|all)\b' app/Http/Controllers/ | \
  grep -v 'FormRequest\|validated('

# Express / Node: handlers using req.body / req.query without zod/joi/yup
grep -rEn 'req\.(body|query|params)' src/ | \
  grep -vE 'parse\(|validate\(|safeParse'

# SQL string concatenation (always bad) — grep's --include uses fnmatch, not brace expansion
grep -rEn '(SELECT|INSERT|UPDATE|DELETE).*\+.*\$|\\?.*concat' \
  --include='*.ts' --include='*.tsx' --include='*.php' --include='*.js' .

# Shell-out from app code (path for command injection)
grep -rEn 'exec\(|shell_exec\(|proc_open\(|spawn\(' \
  --include='*.ts' --include='*.php' --include='*.js' .

Also look at audit log coverage: every controller endpoint should map to an explicit validation rule set.

Incorrect

// ❌ Direct use of request input — type cast is not validation
app.get('/users', async (req, res) => {
  const limit  = parseInt(req.query.limit as string);
  const search = req.query.q as string;
  const [rows] = await pool.query(
    `SELECT * FROM users WHERE name LIKE '%${search}%' LIMIT ${limit}`,    // SQL injection
  );
  res.json(rows);
});
// ❌ Laravel: same problem, different stack
public function index(Request $request) {
    $sort = $request->input('sort');            // attacker controls
    $users = DB::select("SELECT * FROM users ORDER BY $sort");
    return response()->json($users);
}

Problems:

  • SQL injection in both examples (no parameterization, no allowlist)
  • parseInt of attacker input returns NaN for non-numbers — LIMIT NaN errors leak SQL structure
  • The "cast as string" in TypeScript provides zero runtime validation

Correct

// ✅ Zod schema + parameterized query
import { z } from 'zod';

const ListUsersQuery = z.object({
  limit: z.coerce.number().int().min(1).max(100).default(20),
  q:     z.string().max(80).optional(),
});

// Using `mysql2/promise` — `?` placeholders, parameterized by the driver.
// We use `pool.query()` (client-side escaping) rather than `pool.execute()`
// (server-side prepared statements) because mysql2's prepared statements
// can fail to bind JS numbers to `LIMIT ?` (ER_WRONG_ARGUMENTS in some
// MySQL versions). With `query()` mysql2 escapes the number safely.
// Also: MySQL's default collation (`utf8mb4_0900_ai_ci`) is case-insensitive,
// so plain LIKE matches both 'Asyraf' and 'asyraf' without `LOWER(...)`.
import mysql from 'mysql2/promise';
const pool = mysql.createPool({ /* ... */ });

app.get('/users', async (req, res) => {
  const parsed = ListUsersQuery.safeParse(req.query);
  if (!parsed.success) return res.status(400).json(parsed.error.flatten());

  const { limit, q } = parsed.data;
  const [rows] = await pool.query(
    'SELECT * FROM users WHERE (? IS NULL OR name LIKE ?) LIMIT ?',
    [q ?? null, q ? `%${q}%` : null, limit],
  );
  res.json(rows);
});
// ✅ Laravel FormRequest with allowlisted sort
final class ListUsersRequest extends FormRequest
{
    public function rules(): array {
        return [
            'sort' => ['nullable', Rule::in(['name', 'created_at'])],
            'limit' => ['integer', 'min:1', 'max:100'],
        ];
    }
}

public function index(ListUsersRequest $request) {
    $sort = $request->validated('sort', 'created_at');
    return DB::table('users')->orderBy($sort)->paginate($request->validated('limit', 20));
}

Benefits:

  • Bad input → 400 with a clear message, never reaches the database
  • SQL injection eliminated by parameterization + allowlist
  • Validation is a single auditable location per endpoint

Remediation Strategy

  • Effort: S per endpoint
  • When to pay down:
    • NOW: any endpoint that takes input into a raw SQL string, shell command, or file path
    • This sprint: all unvalidated endpoints in critical paths (auth, payment, profile)
    • Then: lint rules that fail PRs lacking validation schemas
  • Tip: put validation at the boundary (controller / route handler), then trust the validated shape downstream. Don't re-validate the same fields in 5 places.

Reference: OWASP — Input Validation Cheat Sheet · Laravel Validation · Zod

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides a comprehensive and safe framework for identifying, prioritizing, and auditing technical debt in PHP/Laravel and Node/TypeScript/React projects. No security risks or malicious behaviors were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at 4df703d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 5 months ago
metadata
{
  "author": "agent-skills",
  "version": "1.0.0"
}

README badge

README badge for asyrafhussin/agent-skills/technical-debt