AgentStack
SKILL unreviewed MIT Self-run

Supply Chain Sentinel

skill-aajj68-supply-chain-sentinel-supply-chain-sentinel · by aajj68

>

No reviews yet
0 installs
2 views
0.0% view→install

Install

$ agentstack add skill-aajj68-supply-chain-sentinel-supply-chain-sentinel

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Possible prompt-injection directive.

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Supply Chain Sentinel? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Supply Chain Sentinel

Scanner portátil de segurança para detectar ataques de supply chain, dependências maliciosas, injeção de prompt, código obfuscado e padrões suspeitos em projetos de software.

Fluxo de Execução

  1. Leia este arquivo completo antes de começar
  2. Execute o scanner Python via scripts/scanner.py
  3. Complemente com análise manual dos achados de alta severidade
  4. Gere o relatório final em Markdown

Passo 1 — Localizar o Projeto

Se o usuário não especificou o path:

# Tenta caminhos comuns
ls /mnt/user-data/uploads/ 2>/dev/null || true
ls ~/project/ 2>/dev/null || true
pwd

Pergunte ao usuário se não encontrar. O scanner aceita qualquer diretório raiz.


Passo 2 — Executar o Scanner

Copie o script para um local gravável e execute:

cp /mnt/skills/*/supply-chain-sentinel/scripts/scanner.py /tmp/sentinel_scanner.py \
  || cp "$(dirname "$0")/scripts/scanner.py" /tmp/sentinel_scanner.py

python3 /tmp/sentinel_scanner.py --path  --output /tmp/sentinel_report.json

Se o script não estiver acessível via path relativo, escreva-o em disco usando o conteúdo da seção ## Scanner Script abaixo, salve em /tmp/sentinel_scanner.py e execute normalmente.


Passo 3 — Análise Manual Complementar

Após o scanner, faça buscas direcionadas nos achados críticos:

3a. Verificar setup.py / pyproject.toml suspeitos

# Chamadas de rede no setup
grep -rn "urllib\|requests\|socket\|http" /setup.py \
  /pyproject.toml 2>/dev/null

# Exec/eval no install
grep -rn "exec\|eval\|compile\|__import__" /setup.py 2>/dev/null

3b. Strings Base64 / Ofuscadas

grep -rEn "base64\.(b64decode|decodebytes)|\\\\x[0-9a-f]{2}|eval\(.*decode" \
   --include="*.py" --include="*.js" --include="*.ts" 2>/dev/null | head -50

3c. Prompt Injection em arquivos de config/prompt

grep -rniP "(ignore previous|disregard|you are now|forget your|override instructions|\
act as if|new system prompt|jailbreak|\\[INST\\]|)" \
   --include="*.txt" --include="*.md" --include="*.json" \
  --include="*.yaml" --include="*.yml" --include="*.toml" 2>/dev/null

3d. Caracteres Invisíveis / Unicode Suspeito

# Zero-width chars e bidi override (CVE-2021-42574 "Trojan Source")
grep -rPn "[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\u200b-\u200f\u202a-\u202e\u2060-\u2064\ufeff]" \
   --include="*.py" --include="*.js" --include="*.ts" 2>/dev/null | head -30

3e. Exfiltração de Credenciais / Env Vars

grep -rEn "(os\.environ|getenv|AWS_|GITHUB_TOKEN|api.key|password|secret)" \
   --include="*.py" --include="*.js" 2>/dev/null \
  | grep -v "test\|spec\|example\|\.env\.example" | head -40

Passo 4 — Consolidar e Gerar Relatório

Leia o JSON gerado pelo scanner e os resultados manuais, depois produza um relatório Markdown estruturado:

# 🛡️ Security Audit Report — Supply Chain Sentinel

**Projeto:** 
**Data:** 
**Severidade Geral:** 🔴 CRÍTICA / 🟠 ALTA / 🟡 MÉDIA / 🟢 BAIXA

---

## Resumo Executivo

## Achados por Categoria
### 🔴 Críticos
### 🟠 Altos  
### 🟡 Médios
### 🟢 Informativos

## Dependências Suspeitas

## Recomendações

## Metodologia

Scanner Script

Se o script não estiver acessível via filesystem, crie-o com este conteúdo em /tmp/sentinel_scanner.py:

#!/usr/bin/env python3
"""
Supply Chain Sentinel — scanner.py
Portable security scanner for supply chain attacks, malicious deps, prompt injection.
"""

import os, sys, re, json, hashlib, argparse, ast
from pathlib import Path
from datetime import datetime
from typing import Any

# ── Typosquatting: common targets (PyPI top 100 + security-relevant) ──────────
PYPI_POPULAR = {
    "requests","numpy","pandas","flask","django","fastapi","boto3","pydantic",
    "sqlalchemy","celery","redis","pymongo","psycopg2","cryptography","paramiko",
    "httpx","aiohttp","click","typer","rich","setuptools","pip","wheel","twine",
    "pytest","black","mypy","flake8","pylint","isort","poetry","virtualenv",
    "pillow","matplotlib","scipy","sklearn","torch","tensorflow","keras",
    "openai","anthropic","langchain","transformers","huggingface-hub",
    "ansible","fabric","invoke","nox","tox","coverage","hypothesis",
    "uvicorn","gunicorn","starlette","pydantic-settings","alembic",
}

NPM_POPULAR = {
    "react","vue","angular","express","lodash","axios","webpack","babel",
    "typescript","jest","eslint","prettier","next","nuxt","vite","rollup",
    "moment","dayjs","chalk","commander","dotenv","cors","helmet","jsonwebtoken",
    "bcrypt","mongoose","sequelize","prisma","graphql","apollo","socket.io",
    "nodemailer","multer","sharp","uuid","crypto-js","node-fetch","cross-fetch",
}

# ── Suspicious code patterns ──────────────────────────────────────────────────
SUSPICIOUS_PATTERNS = [
    # Obfuscation / dynamic execution
    (r'eval\s*\(\s*(base64|__import__|bytes|chr\()', "CRITICAL", "eval com payload codificado"),
    (r'exec\s*\(\s*(base64|__import__|compile)', "CRITICAL", "exec com payload suspeito"),
    (r'__import__\s*\(\s*["\']os["\'].*system', "CRITICAL", "import dinâmico + os.system"),
    (r'base64\.b64decode\s*\([^)]{20,}\)', "HIGH", "blob base64 decodificado em runtime"),
    (r'\\x[0-9a-fA-F]{2}(\\x[0-9a-fA-F]{2}){8,}', "HIGH", "sequência hex longa (shellcode?)"),
    (r'chr\(\d+\)\s*\+\s*chr\(\d+\)', "MEDIUM", "string montada via chr() (ofuscação)"),
    # Network exfiltration
    (r'(requests|urllib|httpx|aiohttp)\.(get|post)\s*\(["\']https?://(?!localhost|127\.0\.0\.1)',
     "HIGH", "chamada HTTP para host externo"),
    (r'socket\.connect\s*\(\s*\(["\'][0-9]{1,3}\.[0-9]{1,3}', "CRITICAL", "conexão socket direta a IP"),
    (r'dns\.(resolver|query)|dnslib', "MEDIUM", "uso de DNS (possível tunneling)"),
    # Credential harvesting
    (r'os\.environ\.get\s*\(\s*["\'](?:AWS|GITHUB|TOKEN|SECRET|PASSWORD|API_KEY)',
     "HIGH", "leitura de variável de ambiente sensível"),
    (r'open\s*\(\s*["\'][^"\']*\.ssh[/\\\\]', "CRITICAL", "acesso a chaves SSH"),
    (r'open\s*\(\s*["\'][^"\']*(?:\.aws|credentials|\.netrc)["\']', "CRITICAL", "acesso a arquivo de credenciais"),
    # Prompt injection markers
    (r'(?i)(ignore\s+(?:previous|all)\s+instructions?|disregard\s+(?:prior|previous)|'
     r'you\s+are\s+now\s+(?:a|an)|forget\s+(?:your|all)\s+(?:previous|prior)|'
     r'new\s+system\s+prompt|override\s+(?:your\s+)?instructions)',
     "HIGH", "prompt injection marker"),
    (r'(?i)(\[INST\]||||\{\{.*role.*system.*\}\})',
     "HIGH", "template de prompt injetado"),
    # Reverse shell / RCE
    (r'subprocess\.(Popen|run|call)\s*\(\s*\[?\s*["\'](?:bash|sh|cmd|powershell)',
     "CRITICAL", "execução de shell via subprocess"),
    (r'os\.system\s*\(\s*["\'][^"\']*(?:curl|wget|nc |netcat)',
     "CRITICAL", "download/conexão via shell"),
    # Persistence
    (r'(?:crontab|/etc/cron|\.bashrc|\.zshrc|\.profile)\s*["\'].*write',
     "HIGH", "escrita em arquivo de inicialização/cron"),
    (r'HKEY_|winreg|Registry', "MEDIUM", "acesso ao registro do Windows"),
]

# ── Prompt injection in non-code files ───────────────────────────────────────
PROMPT_INJECTION_PATTERNS = [
    r'(?i)ignore\s+(previous|all|prior)\s+instructions?',
    r'(?i)disregard\s+(prior|previous|above)',
    r'(?i)you\s+are\s+now\s+(a|an)\s+\w+',
    r'(?i)forget\s+(your|all)',
    r'(?i)new\s+system\s+prompt',
    r'(?i)override\s+instructions',
    r'(?i)act\s+as\s+(if\s+)?you\s+(are|were)',
    r'(?i)\[INST\]|\[\/INST\]',
    r'||',
    r'(?i)jailbreak',
    r'(?i)do\s+anything\s+now\s*\(dan\)',
]

# ── Unicode / invisible char ranges ──────────────────────────────────────────
INVISIBLE_CHARS = re.compile(
    r'[\u200b-\u200f\u202a-\u202e\u2060-\u2064\ufeff\u00ad]'
)

BIDI_OVERRIDE = re.compile(r'[\u202a-\u202e\u2066-\u2069]')

# ── Dependency confusion indicators ──────────────────────────────────────────
INTERNAL_NAME_PATTERNS = [
    r'^(?:internal|private|corp|company|org|local|dev|staging|prod)-',
    r'-(?:internal|private|corp|company|local|dev)$',
    r'^(?:lib|pkg|mod|util|helper|common|shared|core)-[a-z]+-[a-z]+$',
]

# ──────────────────────────────────────────────────────────────────────────────

class Finding:
    def __init__(self, severity, category, title, description, file=None, line=None, evidence=None):
        self.severity = severity        # CRITICAL HIGH MEDIUM LOW INFO
        self.category = category        # supply-chain | malicious-code | prompt-injection | ...
        self.title = title
        self.description = description
        self.file = file
        self.line = line
        self.evidence = evidence or ""

    def to_dict(self):
        return {k: v for k, v in self.__dict__.items() if v is not None and v != ""}

def levenshtein(a: str, b: str) -> int:
    if len(a)  list:
    name_lower = name.lower().replace("-", "").replace("_", "")
    candidates = []
    for known in known_set:
        known_norm = known.lower().replace("-", "").replace("_", "")
        if name_lower == known_norm:
            continue  # exact match, not a typo
        dist = levenshtein(name_lower, known_norm)
        if 0 === 2:
            findings.append(Finding(
                "HIGH", "supply-chain",
                "setup.py com padrões suspeitos",
                f"setup.py contém combinação suspeita: {found_dangerous}",
                "setup.py", None,
                f"Keywords: {found_dangerous}"
            ))

def scan_npm_deps(project_root: Path, findings: list):
    """Scan Node.js dependency files."""
    for pkg_json in project_root.rglob("package.json"):
        # Skip node_modules
        if "node_modules" in pkg_json.parts:
            continue
        try:
            data = json.loads(pkg_json.read_text(errors="replace"))
        except Exception:
            continue

        rel = str(pkg_json.relative_to(project_root))
        all_deps = {}
        all_deps.update(data.get("dependencies", {}))
        all_deps.update(data.get("devDependencies", {}))

        for pkg, version in all_deps.items():
            # URL / git installs
            if re.match(r'^(git\+|https?://|github:|bitbucket:|gitlab:)', str(version)):
                findings.append(Finding(
                    "HIGH", "supply-chain",
                    f"npm: dependência via URL/git — '{pkg}'",
                    "Bypassa o registro npm; não há verificação de integridade automática",
                    rel, None, f"{pkg}: {version}"
                ))

            # Typosquatting
            hits = detect_typosquatting(pkg, NPM_POPULAR)
            if hits:
                findings.append(Finding(
                    "HIGH", "supply-chain",
                    f"npm: possível typosquatting — '{pkg}'",
                    f"Similar a: {', '.join(h[0] for h in hits[:3])}",
                    rel, None, pkg
                ))

            # Local path installs (dependency confusion vector)
            if str(version).startswith("file:"):
                findings.append(Finding(
                    "MEDIUM", "supply-chain",
                    f"npm: dependência local — '{pkg}'",
                    "Pacote instalado por path local; risco de confusion se o nome existir no registry",
                    rel, None, f"{pkg}: {version}"
                ))

        # lifecycle scripts
        scripts = data.get("scripts", {})
        risky_scripts = {k: v for k, v in scripts.items()
                         if k in ("preinstall", "postinstall", "install", "prepare")
                         and any(kw in v for kw in ["curl", "wget", "node -e", "python", "bash", "sh "])}
        for script_name, script_val in risky_scripts.items():
            findings.append(Finding(
                "CRITICAL", "supply-chain",
                f"npm lifecycle script suspeito: '{script_name}'",
                "Scripts de install executam código arbitrário no momento da instalação",
                rel, None, f"{script_name}: {script_val}"
            ))

def scan_other_manifests(project_root: Path, findings: list):
    """Scan Go, Rust, Java manifests."""
    # go.mod — replace directives pointing to local/unknown sources
    go_mod = project_root / "go.mod"
    if go_mod.exists():
        for i, line in enumerate(go_mod.read_text().splitlines(), 1):
            if re.match(r'\s*replace\s+', line):
                findings.append(Finding(
                    "MEDIUM", "supply-chain",
                    "go.mod: diretiva replace",
                    "Diretivas 'replace' podem apontar para forks maliciosos",
                    "go.mod", i, line.strip()
                ))

    # Cargo.toml — path/git deps
    for cargo in project_root.rglob("Cargo.toml"):
        if ".cargo" in cargo.parts:
            continue
        content = cargo.read_text(errors="replace")
        rel = str(cargo.relative_to(project_root))
        for i, line in enumerate(content.splitlines(), 1):
            if re.search(r'git\s*=\s*"', line):
                findings.append(Finding(
                    "MEDIUM", "supply-chain",
                    "Cargo: dependência via git",
                    "Dependências git não têm garantia de imutabilidade (sem hash fixo)",
                    rel, i, line.strip()
                ))

def scan_code_patterns(project_root: Path, findings: list):
    """Scan source code for suspicious patterns."""
    code_extensions = {".py", ".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs",
                       ".rb", ".php", ".sh", ".bash", ".ps1"}
    skip_dirs = {"node_modules", ".git", "__pycache__", ".venv", "venv",
                 "env", ".tox", "dist", "build", ".mypy_cache", "site-packages"}

    for fpath in project_root.rglob("*"):
        if fpath.suffix not in code_extensions:
            continue
        if any(skip in fpath.parts for skip in skip_dirs):
            continue
        if fpath.stat().st_size > 2_000_000:  # skip files > 2MB
            continue

        try:
            content = fpath.read_text(errors="replace")
        except Exception:
            continue

        rel = str(fpath.relative_to(project_root))

        # Pattern scan
        for pattern, severity, description in SUSPICIOUS_PATTERNS:
            for m in re.finditer(pattern, content, re.MULTILINE):
                line_no = content[:m.start()].count('\n') + 1
                findings.append(Finding(
                    severity, "malicious-code",
                    description,
                    f"Padrão suspeito encontrado em {rel}:{line_no}",
                    rel, line_no,
                    m.group(0)[:120]
                ))

        # Invisible / bidi chars
        for m in INVISIBLE_CHARS.finditer(content):
            line_no = content[:m.start()].count('\n') + 1
            findings.append(Finding(
                "HIGH", "obfuscation",
                "Caractere invisível/Unicode suspeito",
                "Pode indicar 'Trojan Source' (CVE-2021-42574) ou ofuscação",
                rel, line_no,
                f"U+{ord(m.group()):04X} na coluna {m.start() - content.rfind(chr(10), 0, m.start())}"
            ))

        # Large base64 blobs
        b64_matches = re.findall(r'[A-Za-z0-9+/]{100,}={0,2}', content)
        if b64_matches:
            for blob in b64_matches[:3]:
                line_no = content.find(blob)
                line_no = content[:line_no].count('\n') + 1 if line_no >= 0 else 0
                findings.append(Finding(
                    "MEDIUM", "obfuscation",
                    "Blob base64 grande encontrado",
                    "Blobs grandes embutidos podem ser payloads codificados",
                    rel, line_no, blob[:60] + "..."
                ))

def scan_prompt_injection(project_root: Path, findings: list):
    """Scan config/doc/prompt files for prompt injection."""
    text_extensions = {
        ".txt", ".md", ".rst", ".yaml", ".yml", ".toml",
        ".json", ".env", ".cfg", ".ini", ".conf",
        ".prompt", ".system", ".jin

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [aajj68](https://github.com/aajj68)
- **Source:** [aajj68/supply-chain-sentinel](https://github.com/aajj68/supply-chain-sentinel)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.