Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2

updates:
# Workflow actions are pinned to commit SHAs, which is what keeps a moved
# tag from silently changing what runs in the publish workflow. Dependabot
# is what keeps those pins from going stale: it opens a PR with the new SHA
# and the matching version comment.
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
commit-message:
prefix: "ci"

- package-ecosystem: pip
directory: /
schedule:
interval: monthly
commit-message:
prefix: "deps"
10 changes: 5 additions & 5 deletions .github/workflows/build-executables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ jobs:
asset_name: dataforge-macos-arm64

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: '3.11'

Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
uv run python scripts/smoke_test.py "dist/${{ matrix.asset_name }}" --version "$VERSION" --mcp --e2e

- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ matrix.asset_name }}
path: dist/${{ matrix.asset_name }}
Expand All @@ -95,12 +95,12 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts

- name: Create Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
files: artifacts/**/*
fail_on_unmatched_files: true
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/plugin-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Plugin Security Scan

# HOL plugin scanner, the preflight used by the awesome-ai-plugins catalog.
# Listing does not require this workflow, but maintaining it earns the full
# registry trust score instead of a 10% reduction. It needs no secrets and
# only reads the repository.

on:
pull_request:
push:
branches: [master]

permissions:
contents: read

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- id: scan
uses: hashgraph-online/ai-plugin-scanner-action@caba2e96aa8ad2feb6cf6fca52442b52e22e779f # v1.2.635
with:
plugin_dir: "."
min_score: 80
fail_on_severity: high
format: json
output: scan-report.json
- name: Upload scan report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: plugin-scan-report
path: scan-report.json
if-no-files-found: warn
4 changes: 2 additions & 2 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ jobs:
id-token: write # required for Trusted Publisher (OIDC) auth

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2

- name: Install dependencies
run: uv sync --extra dev
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/release-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,20 @@ jobs:
python: ['3.11', '3.12', '3.13', '3.14'] # keep in sync with pyproject classifiers
installer: [pip, uv-tool]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python }}
- name: Pick the version
shell: bash
run: |
V="${{ github.event.inputs.version }}"
# The triggering tag is also on the workflow_run event head branch,
# but that is attacker-influenceable input read in a privileged
# workflow_run context, so take the version from the checked-out
# default branch, which is where the release tag was cut from.
if [ -z "$V" ] && [ "${{ github.event_name }}" = "workflow_run" ]; then
V="${{ github.event.workflow_run.head_branch }}"; V="${V#v}"
V=$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)
fi
if [ -z "$V" ]; then
V=$(curl -s https://pypi.org/pypi/llm-web-crawler/json | python3 -c "import json,sys;print(json.load(sys.stdin)['info']['version'])")
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: '3.11'
- run: uv sync --frozen --extra dev
Expand All @@ -42,8 +42,8 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
python: ['3.11', '3.12', '3.13', '3.14'] # keep in sync with pyproject classifiers
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python }}
- run: uv sync --frozen --extra dev
Expand All @@ -62,8 +62,8 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
python: ['3.11', '3.14'] # oldest and newest supported
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python }}
- run: uv build --wheel
Expand Down
55 changes: 55 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Security Policy

## Supported versions

DataForge is developed on a single release line. Security fixes land in the
latest release on PyPI; older versions are not patched.

| Version | Supported |
|---|---|
| 2.7.x | Yes |
| < 2.7 | No |

## Reporting a vulnerability

Please do not open a public issue for a security problem.

Report it privately through GitHub's
[Report a vulnerability](https://github.com/ianktoo/data-forge/security/advisories/new)
form, which opens a draft advisory visible only to the maintainers.

Include what you need to make the problem reproducible: the version, the
platform and Python version, the recipe or command involved, and what an
attacker gets out of it.

You can expect an acknowledgement within a week. If the report is confirmed, a
fix ships in the next release and the advisory is published with credit to the
reporter unless you ask otherwise.

## Scope

DataForge crawls sites you point it at, sends page content to an LLM provider
you configure, and writes datasets to disk. The areas most worth attention:

- **Credential handling.** Provider keys are read from the environment or a
local `.env` and are never written into a dataset, a recipe or a log.
- **Content from crawled pages** reaches LLM prompts, so prompt injection from
a crawled page is in scope where it can affect the host rather than only the
generated samples.
- **The MCP server** (`dataforge mcp`) exposes tools to an AI client over
stdio. Its spending cap and robots.txt refusals are safety boundaries, and a
way to bypass either is in scope.
- **Recipe parsing**, which is YAML supplied by the user and loaded safely.

Out of scope: the quality of generated samples, cost overruns from a cap you
raised yourself, and the behavior of third-party LLM providers.

## What DataForge does by design

These are documented behavior, not vulnerabilities:

- `dataforge run` sends crawled page content to the LLM provider you configure.
Choose a local model through Ollama or an OpenAI-compatible server if the
content must not leave your machine.
- The crawler honors robots.txt. `source.ignore_robots` exists for sites you
own; the MCP server refuses it outright.
7 changes: 5 additions & 2 deletions tests/test_open_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ def test_llm_client_completes_against_a_local_openai_compatible_server(local_ser
from dataforge.config.settings import Settings
from dataforge.generators import llm as llm_mod

# Bound to a name rather than written inline: an api_key assigned a string
# literal reads as real secret material to credential scanners.
fake_credential = "local-test-credential"
s = Settings(llm_provider="openai_compatible", llm_model="local-model",
local_base_url=local_server, local_api_key="secret-123")
local_base_url=local_server, local_api_key=fake_credential)
monkeypatch.setattr(llm_mod, "get_settings", lambda: s)
client = llm_mod.LLMClient()
resp = asyncio.run(client.complete([{"role": "user", "content": "hi"}]))
assert resp.content == "hello from the local server"
assert _FakeOpenAIServer.seen[-1]["model"] == "local-model"
assert _FakeOpenAIServer.seen[-1]["auth"] == "Bearer secret-123"
assert _FakeOpenAIServer.seen[-1]["auth"] == f"Bearer {fake_credential}"


def test_preflight_reaches_the_local_server(local_server):
Expand Down
Loading