All skills
getsentry avatar

/security-review

@3482d8d official
by Sentrygetsentry/skills1k stars
53

Security code review for vulnerabilities. Use when asked to "security review", "find vulnerabilities", "check for security issues", "audit security", "OWASP review", or review code for injection, XSS, authentication, authorization, cryptography issues. Provides systematic review with confidence-based reporting.

Use this Skill: https://skilld.dev/gh/getsentry/skills/security-review

This session only. Nothing lands on disk.

referencesssrf.md

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

Server-Side Request Forgery (SSRF) Prevention Reference

Overview

SSRF vulnerabilities allow attackers to induce the server-side application to make HTTP requests to an arbitrary domain of the attacker's choosing. This can be used to:

  • Access internal services not exposed to the internet
  • Read cloud metadata (AWS, GCP, Azure credentials)
  • Scan internal networks
  • Bypass firewalls and access controls
  • Exploit internal services with known vulnerabilities

Attack Scenarios

Cloud Metadata Access (AWS)

# Attacker provides URL:
http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name

# Server fetches and returns AWS credentials:
{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "...",
  "Token": "..."
}

Internal Service Access

# Attacker provides URL:
http://localhost:8080/admin/delete-all
http://internal-service.local/sensitive-data

# Server makes request to internal service that trusts localhost

Port Scanning

# Attacker probes internal network:
http://192.168.1.1:22    # SSH
http://192.168.1.1:3306  # MySQL
http://192.168.1.1:6379  # Redis

Prevention Strategies

1. Input Validation (Allowlist)

Preferred when target hosts are known.

# VULNERABLE: No validation
def fetch_url(url):
    return requests.get(url).content

# SAFE: Allowlist of permitted domains
ALLOWED_DOMAINS = {'api.example.com', 'cdn.example.com'}

def fetch_url(url):
    parsed = urlparse(url)

    # Validate scheme
    if parsed.scheme not in ('http', 'https'):
        raise ValueError("Invalid URL scheme")

    # Validate domain against allowlist
    if parsed.hostname not in ALLOWED_DOMAINS:
        raise ValueError("Domain not allowed")

    return requests.get(url).content

2. Block Internal Networks (Denylist)

Additional defense layer when allowlist isn't practical.

import ipaddress
import socket

BLOCKED_RANGES = [
    ipaddress.ip_network('127.0.0.0/8'),      # Loopback
    ipaddress.ip_network('10.0.0.0/8'),       # Private
    ipaddress.ip_network('172.16.0.0/12'),    # Private
    ipaddress.ip_network('192.168.0.0/16'),   # Private
    ipaddress.ip_network('169.254.0.0/16'),   # Link-local (metadata)
    ipaddress.ip_network('0.0.0.0/8'),        # Current network
    ipaddress.ip_network('100.64.0.0/10'),    # Shared address space
    ipaddress.ip_network('192.0.0.0/24'),     # IETF Protocol
    ipaddress.ip_network('192.0.2.0/24'),     # Documentation
    ipaddress.ip_network('198.51.100.0/24'),  # Documentation
    ipaddress.ip_network('203.0.113.0/24'),   # Documentation
    ipaddress.ip_network('224.0.0.0/4'),      # Multicast
    ipaddress.ip_network('240.0.0.0/4'),      # Reserved
]

def is_internal_ip(ip_str):
    try:
        ip = ipaddress.ip_address(ip_str)
        return any(ip in network for network in BLOCKED_RANGES)
    except ValueError:
        return True  # Invalid IP, block it

def validate_url(url):
    parsed = urlparse(url)

    # Validate scheme
    if parsed.scheme not in ('http', 'https'):
        raise ValueError("Invalid URL scheme")

    # Resolve hostname to IP
    hostname = parsed.hostname
    if not hostname:
        raise ValueError("Invalid URL")

    # Check for IP address directly in URL
    try:
        ip = ipaddress.ip_address(hostname)
        if is_internal_ip(str(ip)):
            raise ValueError("Internal IP addresses not allowed")
    except ValueError:
        # It's a hostname, resolve it
        try:
            ip = socket.gethostbyname(hostname)
            if is_internal_ip(ip):
                raise ValueError("Domain resolves to internal IP")
        except socket.gaierror:
            raise ValueError("Could not resolve hostname")

    return True

3. Disable Redirects

# VULNERABLE: Follows redirects (can bypass IP checks)
response = requests.get(url, allow_redirects=True)
# Attacker: http://attacker.com/redirect -> http://169.254.169.254/

# SAFE: Don't follow redirects automatically
response = requests.get(url, allow_redirects=False)

# If redirects needed, validate each location
def safe_fetch(url, max_redirects=5):
    for _ in range(max_redirects):
        validate_url(url)  # Validate before each request
        response = requests.get(url, allow_redirects=False)

        if response.status_code in (301, 302, 303, 307, 308):
            url = response.headers.get('Location')
            if not url:
                raise ValueError("Redirect without Location")
            continue

        return response

    raise ValueError("Too many redirects")

4. DNS Rebinding Protection

import socket
import time

def safe_fetch_with_dns_pinning(url):
    parsed = urlparse(url)
    hostname = parsed.hostname

    # Resolve DNS and pin the IP
    ip = socket.gethostbyname(hostname)

    # Validate IP is not internal
    if is_internal_ip(ip):
        raise ValueError("Internal IP not allowed")

    # Make request directly to IP with Host header
    # This prevents DNS rebinding attacks
    modified_url = url.replace(hostname, ip)
    headers = {'Host': hostname}

    response = requests.get(
        modified_url,
        headers=headers,
        allow_redirects=False,
        verify=True  # Still verify TLS with original hostname
    )

    return response

5. Cloud Metadata Protection

AWS IMDSv2
# Require IMDSv2 (token-based) - mitigates SSRF
aws ec2 modify-instance-metadata-options \
    --instance-id i-1234567890abcdef0 \
    --http-tokens required \
    --http-endpoint enabled
# With IMDSv2, attacker would need two requests:
# 1. PUT to get token (SSRF usually only does GET)
# 2. GET with token in header

# Block metadata IP regardless
if '169.254.169.254' in url or '169.254.170.2' in url:
    raise ValueError("Metadata endpoints not allowed")
GCP
# Block GCP metadata
BLOCKED_HOSTS = [
    'metadata.google.internal',
    'metadata.google.com',
    '169.254.169.254'
]
Azure
# Block Azure metadata
BLOCKED_HOSTS = [
    '169.254.169.254',
    'management.azure.com'
]

Framework-Specific Mitigations

Python (requests)

from urllib.parse import urlparse
import requests

class SafeRequests:
    @staticmethod
    def get(url, **kwargs):
        validate_url(url)
        kwargs['allow_redirects'] = False
        kwargs['timeout'] = (5, 30)  # Connect and read timeout
        return requests.get(url, **kwargs)

Node.js

const dns = require('dns').promises;

async function safeFetch(targetUrl) {
    const parsed = new URL(targetUrl);

    // Validate scheme
    if (!['http:', 'https:'].includes(parsed.protocol)) {
        throw new Error('Invalid scheme');
    }

    // Resolve and check IP
    const addresses = await dns.lookup(parsed.hostname);
    if (isInternalIP(addresses.address)) {
        throw new Error('Internal IP not allowed');
    }

    return fetch(targetUrl, {
        redirect: 'error',
        signal: AbortSignal.timeout(30000)
    });
}

Java

public class SafeURLConnection {
    private static final Set<String> ALLOWED_PROTOCOLS = Set.of("http", "https");

    public static URLConnection openConnection(String urlString) throws IOException {
        URL url = new URL(urlString);

        if (!ALLOWED_PROTOCOLS.contains(url.getProtocol())) {
            throw new SecurityException("Protocol not allowed");
        }

        InetAddress address = InetAddress.getByName(url.getHost());
        if (isInternalIP(address)) {
            throw new SecurityException("Internal IP not allowed");
        }

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setInstanceFollowRedirects(false);
        connection.setConnectTimeout(5000);
        connection.setReadTimeout(30000);

        return connection;
    }
}

Common Bypass Techniques to Block

URL Encoding

# Bypasses:
http://169.254.169.254/  # Normal
http://169%2e254%2e169%2e254/  # URL encoded dots
http://0251.0376.0251.0376/  # Octal
http://0xa9fea9fe/  # Hex
http://2852039166/  # Decimal

# Defense: Normalize and decode URL before validation
from urllib.parse import unquote

def normalize_url(url):
    return unquote(url)

DNS Rebinding

# Attack: Domain initially resolves to public IP, then internal IP
# First request: attacker.com -> 1.2.3.4 (passes validation)
# DNS changes: attacker.com -> 192.168.1.1
# Second request goes to internal IP

# Defense: Pin DNS resolution and re-validate

IPv6

# Bypasses:
http://[::1]/  # localhost
http://[::ffff:127.0.0.1]/  # IPv4-mapped IPv6
http://[0:0:0:0:0:ffff:169.254.169.254]/

# Defense: Check both IPv4 and IPv6 ranges
BLOCKED_RANGES.extend([
    ipaddress.ip_network('::1/128'),        # IPv6 loopback
    ipaddress.ip_network('fc00::/7'),       # IPv6 private
    ipaddress.ip_network('fe80::/10'),      # IPv6 link-local
])

Alternate Representations

# localhost alternatives:
localhost
127.0.0.1
127.0.0.2  # Any 127.x.x.x is loopback
2130706433  # Decimal for 127.0.0.1
0x7f000001  # Hex
0177.0.0.1  # Octal
127.1       # Short form

Grep Patterns for Detection

# URL fetching functions
grep -rn "requests\.get\|requests\.post\|urllib\.request\|urlopen\|fetch\|axios" --include="*.py" --include="*.js"

# URL from user input
grep -rn "request\.args\|request\.form\|request\.json\|req\.query\|req\.body" --include="*.py" --include="*.js" | grep -i "url"

# Potential SSRF sinks
grep -rn "curl_exec\|file_get_contents\|fopen\|readfile" --include="*.php"

# Missing validation
grep -rn "requests\.get(url\|fetch(url" --include="*.py" --include="*.js"

Testing Checklist

  • User-controlled URLs validated against allowlist
  • Internal IP ranges blocked (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  • Cloud metadata IPs blocked (169.254.169.254)
  • IPv6 internal addresses blocked
  • URL redirects not followed blindly
  • DNS rebinding protected against
  • URL encoding/alternate representations handled
  • IMDSv2 required (AWS environments)
  • Timeouts configured to prevent DoS

References

Source: SKILL.md on GitHub

3 alerts2d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub2d

    The skill is a comprehensive security auditing tool designed to help AI agents identify vulnerabilities in codebases. It contains extensive documentation and examples of common security flaws (such as SQL injection, XSS, and supply chain attacks) to guide the analysis process. Automated scanner alerts regarding malware and code injection are false positives triggered by the inclusion of these educational examples within the documentation.

  • Socket2d

    1 alert: gptMalware

  • Snyk2d

    Risk: LOW · No issues

  • Runlayer7mo

    20/22 files flagged

  • ZeroLeaks5mo

    2 findings · Score: 80/100

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

Last checked against GitHub yesterday.

Activeupdated 3 days ago
All 1 allowed tools
Read Grep Glob Bash Task
  • Security
  • code-review
  • vulnerabilities
  • owasp
  • injection
  • xss
  • authentication
  • authorization

README badge

README badge for getsentry/skills/security-review

Performs systematic security code review to identify exploitable vulnerabilities in injection, XSS, authentication, authorization, and cryptography. Reports only high-confidence findings after researching the codebase to confirm attacker-controlled input and framework mitigations, following OWASP guidelines.

Generated from the current SKILL.md.

Does this skill flag theoretical vulnerabilities or only confirmed exploitable issues?
Only HIGH CONFIDENCE findings—clear vulnerable patterns with attacker-controlled input confirmed through codebase research. Theoretical, best-practice, or defense-in-depth issues are not reported.
Will this skill review test files and commented code?
No. Test files, dead code, and commented code are excluded from review unless explicitly requested. Documentation strings are also skipped.
Does this skill flag hardcoded URLs or configuration values as SSRF vulnerabilities?
No. Server-controlled values like Django settings, environment variables, and hardcoded configuration are considered safe. Only user-input URLs are flagged as SSRF.
What languages and frameworks does this skill cover?
Python (Django, Flask, FastAPI), JavaScript/TypeScript (Node, Express, React, Vue, Next.js), Go, Rust, and Java (Spring). It loads language-specific guides to check for framework-mitigated patterns before flagging.
Does this skill check for hardcoded secrets and credentials?
Yes. Hardcoded passwords, API keys, AWS secrets, and private keys are always flagged as Critical severity.

Generated from the current SKILL.md. These answers refresh after source changes.