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
⚠ Flagged1 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.
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
- Leia este arquivo completo antes de começar
- Execute o scanner Python via
scripts/scanner.py - Complemente com análise manual dos achados de alta severidade
- 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.
Write a review
Versions
- v0.1.0 Imported from the upstream source.