diff --git a/skills/007/scripts/score_calculator.py b/skills/007/scripts/score_calculator.py index e9167ed5..efe1b008 100644 --- a/skills/007/scripts/score_calculator.py +++ b/skills/007/scripts/score_calculator.py @@ -403,6 +403,19 @@ def redact_findings_for_report(findings: list[dict]) -> list[dict]: return redacted +def build_safe_scanner_summaries(scanner_summaries: dict[str, dict]) -> dict[str, dict]: + """Return scanner summaries with primitive numeric values only.""" + safe_summaries: dict[str, dict] = {} + + for scanner_name, summary in scanner_summaries.items(): + safe_summaries[scanner_name] = { + "findings": int(summary.get("findings", 0)), + "score": float(summary.get("score", 0)), + } + + return safe_summaries + + def format_text_report( target: str, domain_scores: dict[str, float], @@ -608,6 +621,9 @@ def run_score( all_findings_raw = secrets_findings + dep_findings + inj_findings + quick_findings all_findings = _deduplicate_findings(all_findings_raw) total_findings = len(all_findings) + safe_findings = redact_findings_for_report(all_findings) + safe_total_findings = len(safe_findings) + safe_scanner_summaries = build_safe_scanner_summaries(scanner_summaries) logger.info( "Aggregated %d raw findings -> %d unique (deduplicated)", @@ -657,8 +673,8 @@ def run_score( result=f"final_score={final_score}, verdict={verdict['label']}", details={ "domain_scores": domain_scores, - "total_findings": total_findings, - "scanner_summaries": scanner_summaries, + "total_findings": safe_total_findings, + "scanner_summaries": safe_scanner_summaries, "duration_seconds": round(elapsed, 3), }, ) @@ -671,9 +687,9 @@ def run_score( domain_scores=domain_scores, final_score=final_score, verdict=verdict, - scanner_summaries=scanner_summaries, + scanner_summaries=safe_scanner_summaries, all_findings=all_findings, - total_findings=total_findings, + total_findings=safe_total_findings, elapsed=elapsed, ) @@ -685,8 +701,8 @@ def run_score( domain_scores=domain_scores, final_score=final_score, verdict=verdict, - scanner_summaries=scanner_summaries, - total_findings=total_findings, + scanner_summaries=safe_scanner_summaries, + total_findings=safe_total_findings, elapsed=elapsed, )) diff --git a/skills/instagram/scripts/auth.py b/skills/instagram/scripts/auth.py index c56a22c9..f4284934 100644 --- a/skills/instagram/scripts/auth.py +++ b/skills/instagram/scripts/auth.py @@ -95,7 +95,7 @@ def wait_for_oauth_code() -> Optional[str]: """Inicia servidor local e espera pelo código de autorização.""" server = HTTPServer(("localhost", OAUTH_REDIRECT_PORT), OAuthCallbackHandler) server.timeout = 120 # 2 minutos - print(f"Aguardando autorização em http://localhost:{OAUTH_REDIRECT_PORT}/callback ...") + print("Aguardando autorização no callback OAuth local...") print("(Timeout: 2 minutos)\n") while OAuthCallbackHandler.authorization_code is None: @@ -297,8 +297,7 @@ async def setup() -> None: ) print("\nAbrindo browser para autorização...") - print(f"App ID em uso: {_mask_secret(app_id)}") - print("A URL de autorização não será exibida para evitar vazamento de credenciais.\n") + print("A URL de autorização e o App ID não serão exibidos para evitar vazamento de credenciais.\n") webbrowser.open(auth_url) # Esperar callback diff --git a/skills/loki-mode/examples/todo-app-generated/backend/package-lock.json b/skills/loki-mode/examples/todo-app-generated/backend/package-lock.json index c19835a5..b4d6c0e2 100644 --- a/skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +++ b/skills/loki-mode/examples/todo-app-generated/backend/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "better-sqlite3": "^12.8.0", "cors": "^2.8.5", - "express": "^4.18.2" + "express": "^4.18.2", + "express-rate-limit": "^8.3.1" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", @@ -685,6 +686,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -885,6 +904,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/skills/loki-mode/examples/todo-app-generated/backend/package.json b/skills/loki-mode/examples/todo-app-generated/backend/package.json index 11cc62a7..4cec7d90 100644 --- a/skills/loki-mode/examples/todo-app-generated/backend/package.json +++ b/skills/loki-mode/examples/todo-app-generated/backend/package.json @@ -11,7 +11,8 @@ "dependencies": { "better-sqlite3": "^12.8.0", "cors": "^2.8.5", - "express": "^4.18.2" + "express": "^4.18.2", + "express-rate-limit": "^8.3.1" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", diff --git a/skills/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts b/skills/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts index 562b623e..fba37278 100644 --- a/skills/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts +++ b/skills/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts @@ -1,11 +1,11 @@ import { Router, Request, Response } from 'express'; +import { rateLimit } from 'express-rate-limit'; import { getDatabase } from '../db'; import { ApiResponse, Todo } from '../types/index'; const router = Router(); const WINDOW_MS = 60_000; const MAX_REQUESTS_PER_WINDOW = 60; -const requestCounts = new Map(); const db = getDatabase(); type TodoRow = Omit & { completed: number }; @@ -17,27 +17,15 @@ function toTodo(row: TodoRow): Todo { }; } -function rateLimit(req: Request, res: Response, next: () => void): void { - const now = Date.now(); - const clientKey = req.ip || 'unknown'; - const entry = requestCounts.get(clientKey); - - if (!entry || entry.resetAt <= now) { - requestCounts.set(clientKey, { count: 1, resetAt: now + WINDOW_MS }); - next(); - return; - } - - if (entry.count >= MAX_REQUESTS_PER_WINDOW) { - res.status(429).json({ error: 'Too many requests. Please retry later.' }); - return; - } - - entry.count += 1; - next(); -} - -router.use(rateLimit); +router.use( + rateLimit({ + windowMs: WINDOW_MS, + limit: MAX_REQUESTS_PER_WINDOW, + standardHeaders: 'draft-8', + legacyHeaders: false, + message: { error: 'Too many requests. Please retry later.' }, + }) +); // GET /api/todos - Retrieve all todos router.get('/todos', (_req: Request, res: Response): void => { diff --git a/skills/whatsapp-cloud-api/assets/boilerplate/python/app.py b/skills/whatsapp-cloud-api/assets/boilerplate/python/app.py index 3547b4d3..ef47c250 100644 --- a/skills/whatsapp-cloud-api/assets/boilerplate/python/app.py +++ b/skills/whatsapp-cloud-api/assets/boilerplate/python/app.py @@ -67,7 +67,7 @@ async def handle_incoming_message(message: dict) -> None: from_number = message["from"] content = extract_message_content(message) - logger.info("Received WhatsApp message type=%s message_id=%s", content["type"], message["id"]) + logger.info("Received WhatsApp message") # Mark as read await whatsapp.mark_as_read(message["id"]) @@ -84,7 +84,7 @@ async def handle_incoming_message(message: dict) -> None: await whatsapp.send_text(from_number, "Recebi sua escolha com sucesso.") case "image" | "document" | "video" | "audio": - await whatsapp.send_text(from_number, f"Recebi sua midia ({content['type']}).") + await whatsapp.send_text(from_number, "Recebi sua midia com sucesso.") case _: await whatsapp.send_text(from_number, "Desculpe, nao entendi. Como posso ajudar?") diff --git a/tools/scripts/convert_html_to_markdown.py b/tools/scripts/convert_html_to_markdown.py index 1615b172..31c0cd4f 100644 --- a/tools/scripts/convert_html_to_markdown.py +++ b/tools/scripts/convert_html_to_markdown.py @@ -11,10 +11,99 @@ import re import sys import urllib.request import urllib.error +from html import unescape +from html.parser import HTMLParser from pathlib import Path from typing import Dict, Optional, Tuple from urllib.parse import urlparse, urljoin + +class MarkdownHTMLParser(HTMLParser): + """Convert a constrained subset of HTML into markdown without regex tag stripping.""" + + def __init__(self) -> None: + super().__init__(convert_charrefs=True) + self._parts: list[str] = [] + self._ignored_tag: Optional[str] = None + self._ignored_depth = 0 + self._current_link: Optional[str] = None + self._list_depth = 0 + self._in_pre = False + + def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None: + if self._ignored_tag: + if tag == self._ignored_tag: + self._ignored_depth += 1 + return + + if tag in {"script", "style"}: + self._ignored_tag = tag + self._ignored_depth = 1 + return + + attrs_dict = dict(attrs) + + if tag in {"article", "main", "div", "section"}: + self._append("\n") + elif tag == "br": + self._append("\n") + elif tag == "p": + self._append("\n\n") + elif tag in {"h1", "h2", "h3"}: + prefix = {"h1": "# ", "h2": "## ", "h3": "### "}[tag] + self._append(f"\n\n{prefix}") + elif tag in {"ul", "ol"}: + self._list_depth += 1 + self._append("\n") + elif tag == "li": + indent = " " * max(0, self._list_depth - 1) + self._append(f"\n{indent}- ") + elif tag == "a": + self._current_link = attrs_dict.get("href") + self._append("[") + elif tag == "pre": + self._in_pre = True + self._append("\n\n```\n") + elif tag == "code" and not self._in_pre: + self._append("`") + + def handle_endtag(self, tag: str) -> None: + if self._ignored_tag: + if tag == self._ignored_tag: + self._ignored_depth -= 1 + if self._ignored_depth == 0: + self._ignored_tag = None + return + + if tag in {"h1", "h2", "h3", "p"}: + self._append("\n") + elif tag in {"ul", "ol"}: + self._list_depth = max(0, self._list_depth - 1) + self._append("\n") + elif tag == "a": + href = self._current_link or "" + self._append(f"]({href})") + self._current_link = None + elif tag == "pre": + self._in_pre = False + self._append("\n```\n") + elif tag == "code" and not self._in_pre: + self._append("`") + + def handle_data(self, data: str) -> None: + if self._ignored_tag or not data: + return + self._append(unescape(data)) + + def get_markdown(self) -> str: + markdown = "".join(self._parts) + markdown = re.sub(r"\n{3,}", "\n\n", markdown) + return markdown.strip() + + def _append(self, text: str) -> None: + if text: + self._parts.append(text) + def parse_frontmatter(content: str) -> Optional[Dict]: """Parse YAML frontmatter.""" fm_match = re.search(r'^---\s*\n(.*?)\n---', content, re.DOTALL) @@ -128,37 +217,10 @@ def extract_markdown_from_html(html_content: str) -> Optional[str]: def convert_html_to_markdown(html: str) -> str: """Basic HTML to markdown conversion.""" - # Remove scripts and styles - html = re.sub(r']*>.*?', '', html, flags=re.DOTALL | re.IGNORECASE) - html = re.sub(r']*>.*?', '', html, flags=re.DOTALL | re.IGNORECASE) - - # Headings - html = re.sub(r']*>(.*?)', r'# \1', html, flags=re.DOTALL | re.IGNORECASE) - html = re.sub(r']*>(.*?)', r'## \1', html, flags=re.DOTALL | re.IGNORECASE) - html = re.sub(r']*>(.*?)', r'### \1', html, flags=re.DOTALL | re.IGNORECASE) - - # Code blocks - html = re.sub(r']*>]*>(.*?)', r'```\n\1\n```', html, flags=re.DOTALL | re.IGNORECASE) - html = re.sub(r']*>(.*?)', r'`\1`', html, flags=re.DOTALL | re.IGNORECASE) - - # Links - html = re.sub(r']*href="([^"]*)"[^>]*>(.*?)', r'[\2](\1)', html, flags=re.DOTALL | re.IGNORECASE) - - # Lists - html = re.sub(r']*>(.*?)', r'- \1', html, flags=re.DOTALL | re.IGNORECASE) - html = re.sub(r']*>||]*>|', '', html, flags=re.IGNORECASE) - - # Paragraphs - html = re.sub(r']*>(.*?)

', r'\1\n\n', html, flags=re.DOTALL | re.IGNORECASE) - - # Remove remaining HTML tags - html = re.sub(r'<[^>]+>', '', html) - - # Clean up whitespace - html = re.sub(r'\n{3,}', '\n\n', html) - html = html.strip() - - return html + parser = MarkdownHTMLParser() + parser.feed(html) + parser.close() + return parser.get_markdown() def create_minimal_markdown(metadata: Dict, source_url: str) -> str: """Create minimal markdown content from metadata."""