Back to skill

Security audit

Bid Proposal Manager

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent for bid/proposal management, but it needs Review because it can persist and transmit sensitive document content through under-scoped database, Notion, URL-fetching, and embedding-provider paths.

Install only after reviewing the data-flow risks. Use a virtual environment, pin dependencies, bind PostgreSQL to localhost with a unique password, avoid --break-system-packages, disable remote embedding fallback unless explicitly intended, restrict URL fetching to trusted public announcement domains, and confirm before creating or updating Notion pages or storing sensitive proposal content.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/document_parser.py:446
Finding

Arbitrary URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
Dict[str, Any]: """URL에서 웹페이지 파싱""" if not HAS_WEB_PARSER: logger.error("웹 파싱 라이브러리가 설치되지 않음 (requests, BeautifulSoup 필요)") raise ImportError("웹 파싱을 위해 requests와 BeautifulSoup4를 설치하세요.") try: logger.info(f"URL 파싱 시작: {url}") response = requests.get(url, timeout=10) response.encoding = response.apparent_encoding or 'utf-8' response.raise_for_status() soup = BeautifulSoup(response.content, 'html.parser') # 메인 콘텐츠 추출 full_text = self._extract_main_content(soup) return { "metadata": { "source_file": url, "source_type": "url", "parsed_at": datetime.utcnow().isoformat() + "Z", "page_count": 1, "parser_version": self.parser_version }, "content": { "full_text": full_text, "sections": self._extract_sections(full_text), "tables": self._extract_tables_from_html(soup) } } except requests.RequestException as e: logger.error(f"URL 요청 실패: {url}, 오류: {str(e)}") raise ``` ### Technical Analysis The parser accepts any user-provided HTTP or HTTPS URL and passes it directly to `requests.get()`. It does not validate the resolved destination, reject private or reserved addresses, restrict allowed domains, or validate redirect targets. Automatic redirects are enabled by default. Consequently, an apparently public URL can redirect the request to loopback, link-local, private-network, or cloud metadata addresses. The response is also loaded without a maximum body-size limit, creating an additional denial-of-service risk. Fetching public announcement URLs i ...[truncated 1126 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/vectorizer.py:147
Finding

Silent Embedding Provider Fallback Can Exfiltrate Document Content

Content
View full analysis
List[List[float]]: """OpenAI API를 사용해 텍스트를 임베딩""" embeddings = [] # Process in batches to avoid rate limits for i in range(0, len(texts), self.config.batch_size): batch = texts[i:i + self.config.batch_size] response = self.client.embeddings.create( model="text-embedding-3-small", input=batch ) embeddings.extend([item.embedding for item in response.data]) logger.debug(f"Embedded batch {i//self.config.batch_size + 1}") return embeddings ``` ```python def get_embedding_provider() -> EmbeddingProvider: """환경 변수와 설치된 패키지에 따라 임베딩 제공자 선택""" provider_name = os.getenv("EMBEDDING_PROVIDER", "sentence_transformers").lower() # Try the requested provider, with fallback chain providers_to_try = [] if provider_name == "sentence_transformers": providers_to_try = [ ("sentence_transformers", 384), ("openai", 1536), ("ollama", 768), ] elif provider_name == "openai": providers_to_try = [ ("openai", 1536), ("sentence_transformers", 384), ("ollama", 768), ] elif provider_name == "ollama": providers_to_try = [ ("ollama", 768), ("sentence_transformers", 384), ("openai", 1536), ] else: logger.error(f"Unknown embedding provider: {provider_name}") providers_to_try = [ ("sentence_transformers", 384), ("openai", 1536), ("ollama", 768), ] last_error = None for ...[truncated 3221 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vectorizer.py:112
Finding

Configurable Ollama Endpoint Can Receive Sensitive Text over Cleartext HTTP

Content
View full analysis
List[List[float]]: """Ollama를 사용해 텍스트를 임베딩""" import requests embeddings = [] for text in texts: try: response = self.session.post( f"{self.base_url}/api/embeddings", json={"model": "nomic-embed-text", "prompt": text}, timeout=30 ) response.raise_for_status() embedding = response.json()["embedding"] embeddings.append(embedding) except Exception as e: logger.error(f"Failed to embed text with Ollama: {e}") raise return embeddings ``` ### Technical Analysis The `OLLAMA_BASE_URL` environment variable accepts an arbitrary destination. Although the default is loopback, the code neither enforces loopback operation nor requires TLS for remote endpoints. Full document-derived text is placed in the `prompt` field and sent to the configured server. An environment variable is an appropriate configuration mechanis ...[truncated 1273 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
references/setup_guide.md:73
Finding

Unpinned Dependencies Are Installed into the System Python Environment

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
references/setup_guide.md:54
Finding

Database Setup Publishes PostgreSQL with Predictable Credentials

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (46)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

설명 중 일부인 'PDF, HWP, HWPX, DOCX, 웹페이지 형식 지원'은 코드와 일치한다. 그러나 실제 코드의 핵심 기능은 문서/URL을 파싱하여 표준 JSON으로 변환하는 것에 한정된다. 코드에는 임베딩 생성, 벡터 DB 저장, PostgreSQL 연결, pgvector 사용, 시맨틱 검색, 제출서류 검증, Notion API 호출 또는 페이지 생성 로직이 전혀 없다. 따라서 선언된 설명은 실제 코드보다 훨씬 넓은 워크플로우를 주장하고 있으며, 현재 코드 조각은 그중 '문서 파싱' 부분만 구현한 것으로 보인다. 이 차이는 부가 구현 세부사항 수준이 아니라 스킬의 주된 목적과 핵심 능력에 해당하므로 실질적 불일치로 판단된다.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

설명은 문서 수집·파싱·추출·벡터 검색까지 포함한 종합 분석 스킬을 주장하지만, 실제 코드 조각은 notion_builder.py 하나로, 분석 결과 JSON을 읽어 Notion 페이지를 구성하는 후처리/출력 계층에 해당합니다. 코드에는 파일 형식별 파서, OCR/텍스트 추출, 임베딩 생성, 데이터베이스 저장, pgvector 질의, 제출서류 검증 로직이 전혀 없습니다. 반면 Notion API를 통한 페이지 생성·업데이트·블록 추가·데이터베이스 대시보드 조회라는 기능은 설명의 일부인 'Notion 프로젝트 페이지 생성'과는 일치합니다. 그러나 선언된 핵심 목적의 대부분이 이 코드에서 확인되지 않으므로 설명과 실제 동작은 materially mismatched 입니다.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

설명과 코드가 일부 겹치는 부분은 있다. 코드에는 공고문에서 구조화 정보 추출, 제출서류 요구사항 추출, 제출 디렉토리 검증, 체크리스트 생성 기능이 포함되어 있어 '제출서류 검증 및 관련 정보 자동 추출'이라는 일부 목적은 부합한다. 그러나 선언된 핵심 범위의 상당 부분이 실제 코드에 없다. 이 코드는 입력으로 '이미 파싱된' JSON을 받으며, PDF/HWP/HWPX/DOCX/웹페이지를 직접 읽고 파싱하지 않는다. 또한 벡터화, PostgreSQL/pgvector 활용, 시맨틱 검색, Notion 페이지 생성과 같은 설명의 핵심 기능이 전혀 구현되어 있지 않다. 따라서 전체 설명이 실제 코드의 기능을 과장하고 있으며, materially different primary scope로 판단된다.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

설명은 end-to-end 처리 스킬처럼 보이지만, 실제 코드 조각은 벡터 저장·검색 엔진에 가깝습니다. store()는 공고문 원본을 직접 파싱하지 않고 이미 만들어진 analysis.json을 읽어 DB에 적재합니다. init_db()는 PostgreSQL 스키마와 pgvector 인덱스를 만들고, search()/similar()는 임베딩 검색을 수행하며, delete()는 프로젝트 삭제를 수행합니다. 따라서 'PostgreSQL + pgvector로 시맨틱 검색 가능' 부분은 설명과 일치합니다. 그러나 설명의 핵심인 다양한 문서 형식 지원 파싱, 제출서류 검증, 정보 자동 추출, Notion 페이지 생성은 이 코드에 나타나지 않습니다. 즉 일부 설명은 맞지만, 주요 기능 다수가 코드상 확인되지 않아 설명과 실제 동작 간 중요한 불일치가 있습니다.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 67)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 70)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 73)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 76)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 79)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 207)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 257)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 258)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 259)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 313)May include surrounding context.

md
python3 scripts/document_parser.py parse --input announcement.pdf --output parsed.json

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/proposal_analyzer.py (reported line 383)May include surrounding context.

python
'sort_order': len(guidelines)
            })

        return guidelines[:10]  # 최대 10개

    def analyze(self, parsed_data: Dict[str, Any]) -> Dict[str, Any]:
        """파싱된 문서 데이터 분석"""

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises operations that inherently require file access, network access, environment-variable handling, and database interaction, but it does not declare any explicit tool scope or permissions boundaries. In an agent runtime, this ambiguity can lead to overbroad default access, making it easier for the skill to read sensitive files, use secrets, or access external systems beyond what users expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill instructs the agent to create and update Notion pages and to write to a PostgreSQL/pgvector database or local files without explicitly requiring user confirmation or warning that external state will be modified. In an agent setting, this can lead to unintended data creation, overwrites, or persistence of sensitive proposal information if the user only intended analysis rather than side effects.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

This JSON taxonomy uses Korean-language descriptions, field labels, and matching patterns throughout, which effectively constrains the skill behavior to a specific language/locale. The file does not provide any indication of user opt-in, multilingual support, or justification that the skill is intentionally region-specific.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · references/setup_guide.md (reported line 19)May include surrounding context.

createdb biddb

pgvector 확장 활성화

psql -d biddb -c "CREATE EXTENSION IF NOT EXISTS vector;" psql -d biddb -c "CREATE EXTENSION IF NOT EXISTS "uuid-ossp";"

text

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · references/setup_guide.md (reported line 27)May include surrounding context.

bash
# PostgreSQL 설치
sudo apt update
sudo apt install postgresql-15 postgresql-contrib-15

# pgvector 설치

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · references/setup_guide.md (reported line 28)May include surrounding context.

bash
# PostgreSQL 설치
sudo apt update
sudo apt install postgresql-15 postgresql-contrib-15

# pgvector 설치

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · references/setup_guide.md (reported line 34)May include surrounding context.

bash
# PostgreSQL 설치
sudo apt update
sudo apt install postgresql-15 postgresql-contrib-15

# pgvector 설치

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · references/setup_guide.md (reported line 35)May include surrounding context.

bash
# PostgreSQL 설치
sudo apt update
sudo apt install postgresql-15 postgresql-contrib-15

# pgvector 설치

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · references/setup_guide.md (reported line 36)May include surrounding context.

bash
# PostgreSQL 설치
sudo apt update
sudo apt install postgresql-15 postgresql-contrib-15

# pgvector 설치

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 224)May include surrounding context.

md
sudo apt install postgresql-15 postgresql-contrib-15

# pgvector 설치
sudo apt install postgresql-15-pgvector

# DB 생성
sudo -u postgres createdb biddb

Static analysis

No suspicious patterns detected.