Fluid Forge
Get Started
See it run
  • Local (DuckDB)
  • Source-Aligned (Postgres → DuckDB)
  • AI Forge + Data Models
  • GCP (BigQuery)
  • Snowflake Team Collaboration
  • Declarative Airflow
  • Orchestration Export
  • Jenkins CI/CD
  • Universal Pipeline
  • 11-Stage Production Pipeline
  • Catalog Forge End-to-End
CLI Reference
  • Overview
  • Quickstart
  • Examples
  • Your own CI
  • Your own scaffolding
  • Custom validator
  • Apply hook
  • Reference
Demos
  • Overview
  • Architecture
  • GCP (BigQuery)
  • AWS (S3 + Athena)
  • Snowflake
  • Local (DuckDB)
  • Custom Providers
  • Roadmap
GitHub
GitHub
Get Started
See it run
  • Local (DuckDB)
  • Source-Aligned (Postgres → DuckDB)
  • AI Forge + Data Models
  • GCP (BigQuery)
  • Snowflake Team Collaboration
  • Declarative Airflow
  • Orchestration Export
  • Jenkins CI/CD
  • Universal Pipeline
  • 11-Stage Production Pipeline
  • Catalog Forge End-to-End
CLI Reference
  • Overview
  • Quickstart
  • Examples
  • Your own CI
  • Your own scaffolding
  • Custom validator
  • Apply hook
  • Reference
Demos
  • Overview
  • Architecture
  • GCP (BigQuery)
  • AWS (S3 + Athena)
  • Snowflake
  • Local (DuckDB)
  • Custom Providers
  • Roadmap
GitHub
GitHub
  • Introduction

    • Home
    • Getting Started
    • Snowflake Quickstart
    • See it run
    • Forge Data Model
    • Vision & Roadmap
    • Playground
    • FAQ
  • Concepts

    • Concepts
    • Builds, Exposes, Bindings
    • What is a contract?
    • Quality, SLAs & Lineage
    • Governance & Policy
    • Agent Policy (LLM/AI governance)
    • Providers vs Platforms
    • Fluid Forge vs alternatives
  • Data Products

    • Product Types — SDP, ADP, CDP
  • Walkthroughs

    • Walkthrough: Local Development
    • Source-Aligned: Postgres → DuckDB → Parquet
    • AI Forge And Data-Model Journeys
    • Walkthrough: Deploy to Google Cloud Platform
    • Walkthrough: Snowflake Team Collaboration
    • Declarative Airflow DAG Generation - The FLUID Way
    • Generating Orchestration Code from Contracts
    • Jenkins CI/CD for FLUID Data Products
    • Universal Pipeline
    • The 11-Stage Pipeline
    • End-to-End Walkthrough: Catalog → Contract → Transformation
  • CLI Reference

    • CLI Reference
    • fluid init
    • fluid demo
    • fluid forge
    • fluid skills
    • fluid status
    • fluid validate
    • fluid plan
    • fluid apply
    • fluid generate
    • fluid generate artifacts
    • fluid validate-artifacts
    • fluid verify-signature
    • fluid generate-airflow
    • fluid generate-pipeline
    • fluid viz-graph
    • fluid odps
    • fluid odps-bitol
    • fluid odcs
    • fluid export
    • fluid export-opds
    • fluid publish
    • fluid datamesh-manager
    • fluid market
    • fluid import
    • fluid policy
    • fluid policy check
    • fluid policy compile
    • fluid policy apply
    • fluid contract-tests
    • fluid contract-validation
    • fluid diff
    • fluid test
    • fluid verify
    • fluid product-new
    • fluid product-add
    • fluid workspace
    • fluid ide
    • fluid ai
    • fluid memory
    • fluid mcp
    • fluid scaffold-ci
    • fluid scaffold-composer
    • fluid scaffold-ide
    • fluid docs
    • fluid config
    • fluid split
    • fluid bundle
    • fluid auth
    • fluid doctor
    • fluid providers
    • fluid provider-init
    • fluid roadmap
    • fluid version
    • fluid runs
    • fluid retention
    • fluid secrets
    • fluid stats
    • fluid contract
    • fluid ship
    • fluid rollback
    • fluid schedule-sync
    • Catalog adapters

      • Source Catalog Integration (V1.5)
      • BigQuery Catalog
      • Snowflake Horizon Catalog
      • Databricks Unity Catalog
      • Google Dataplex Catalog
      • AWS Glue Data Catalog
      • DataHub Catalog
      • Data Mesh Manager Catalog
    • CLI by task

      • CLI by task
      • Add quality rules
      • Add agent governance
      • Debug a failed pipeline run
      • Switch clouds with one line
  • Recipes

    • Recipes
    • Recipe — add a quality rule
    • Recipe — switch clouds with one line
    • Recipe — tag PII in your schema
  • SDK & Plugins

    • SDK & Plugins
    • Quickstart — your first plugin
    • Examples

      • Runnable examples
      • Example: hello-scaffold — the minimal viable plugin
      • Example: gitlab-ci-scaffold — generate a complete CI project
      • Example: steward-validator — a custom governance rule
      • Example: prod-key-guard — apply-time invariant check
    • Journeys

      • Journeys
      • Your own CI/CD

        • You have your own CI/CD setup, no problem
        • GitLab CI — the bundle template
        • GitHub Actions — the bundle template
        • Jenkins — the bundle template
        • CircleCI — the bundle template
      • You have a strict project layout, no problem
      • You have governance rules, no problem
      • You want a check at apply time, no problem
    • Reference

      • Reference
      • Roles reference
      • Entry points reference
      • Trust model
      • Packaging
      • Companion packages
  • Providers

    • Providers
    • Provider Architecture
    • GCP Provider
    • AWS Provider
    • Snowflake Provider
    • Local Provider
    • Creating Custom Providers
    • Provider Roadmap
  • Advanced

    • Blueprints
    • Governance & Compliance
    • Airflow Integration
    • Built-in And Custom Forge Guidance
    • FLUID Forge Contract GPT Packet
    • Forge Discovery Guide
    • Forge Memory Guide
    • LLM Providers
    • Capability Warnings
    • LiteLLM Backend (opt-in)
    • MCP Server
    • Credential Resolver — Security Model
    • Cost Tracking
    • Agentic Primitives
    • Typed Errors
    • Typed CLI Errors
    • Authoring Forge Tools
    • Source-Aligned Acquisition
    • API Stability — fluid_build.api
    • Guided fluid forge UX
    • V1.5 Catalog Integration — Architecture Deep-Dive
    • V1.5 + V2 Hardening — Release Notes
  • Project

    • Contributing to Fluid Forge
    • Fluid Forge Docs Baseline: CLI 0.8.3
    • Fluid Forge Docs Baseline: CLI 0.8.0
    • Fluid Forge Docs Baseline: CLI 0.7.11
    • Fluid Forge Docs Baseline: CLI 0.7.9
    • Fluid Forge v0.7.1 - Multi-Provider Export Release

Trust model

Plugins are uncontained Python loaded into the CLI process. There is no sandboxing, no per-plugin timeout, no resource limit. Trust in a plugin equals trust in whatever pip resolved when you installed it.

If you treat that as adversarial, you'd never install community plugins. Most users — correctly — treat their pip install source list as a small attack surface they've already chosen to trust (same as their requirements.txt for an application). This page describes:

  1. What the CLI guarantees plugins can't do, regardless of intent or bug.
  2. What the CLI deliberately does not defend against.
  3. How to think about plugin trust in your org.

What the CLI defends against

1. Crashes

Every plugin's load() and invocation is wrapped in try/except. A plugin that raises RuntimeError or ImportError is logged at WARNING and the CLI continues with the next plugin. No exception escapes the CLI.

# CLI loop (simplified, from cli/bootstrap.py / validate.py / apply.py)
for ep in entry_points:
    try:
        plugin = ep.load()
        plugin(args)
    except Exception as e:
        LOG.warning("plugin %s failed: %s", ep.name, redact(str(e)))
        # CLI continues

The CLI never crashes because of a plugin. Worst case: a plugin's intended functionality silently doesn't happen, with the failure logged.

Pinned by tests/test_cli_plugin_hooks.py::TestBootstrapCommands::test_plugin_load_failure_logged_not_raised and siblings.

2. Contract mutation (apply hooks)

Apply hooks receive a copy.deepcopy(contract) — not the live reference. Whatever the hook does to its received contract — modify a field, replace a nested dict, drop a key — is invisible to the rest of fluid apply and to other hooks.

# Simplified from cli/apply.py
import copy

for ep in apply_hook_entry_points:
    isolated = copy.deepcopy(contract)   # ← fresh copy per hook
    ep.load()(contract_dir, isolated, errors)

A buggy hook that "fixes" the contract by mutating it can't poison the rest of the apply. A malicious hook that tries to inject extra consumes: or alter metadata.owner.email mid-apply is silently neutralized.

Pinned by test_apply_hook_receives_deep_copy_of_contract.

Validators and CustomScaffolds do not receive a deep copy — they're contract-read-only by convention, and the cost of deepcopy on every validate-time call was deemed not worth the negligible additional safety. If you want belt-and-braces, make a copy yourself.

3. Credential leak in error messages

The CLI has a SecretRedactingFilter that scrubs args bound to password=%s-style log patterns. That filter handles the common case but doesn't catch free-form text — a plugin's RuntimeError("bad config: api_key=sk_live_AAAAA...") would normally slip through.

The plugin code path pre-redacts all plugin-supplied exception text with redact_secret_text() before it reaches logs or error lists:

# Simplified from cli/apply.py, cli/validate.py, cli/bootstrap.py
try:
    plugin(args)
except Exception as e:
    errors.append(
        redact_secret_text(f"plugin {ep.name!r} raised: {e}")  # ← scrubbed
    )

The redactor catches: password=... / api_key=... / token=... style assignments, Bearer ... tokens, ghp_... / sk_live_... / sk_test_... provider keys, JWT-shaped tokens.

It does not catch: URL-embedded credentials (https://user:pass@host), HTTP Basic Auth headers (Authorization: Basic <base64>), arbitrary org-specific secret shapes. If your plugin handles unusual credential shapes, redact them yourself before raising.

Pinned by TestPluginErrorRedaction (5 tests in test_cli_plugin_hooks.py).

4. Apply-hook override is explicit and audited

The single CLI flag that downgrades apply-hook errors to warnings is --force-pattern-drift. Using it:

  • Does not silently bypass the hook — every error still appears, as a WARN.
  • Logs apply hook drift ignored (--force-pattern-drift): ... to stdout / stderr / journald, whatever the deploy environment captures.
  • Is audit-able after the fact.

There's no per-plugin override flag. If a hook needs a custom escape, the hook itself reads an env var:

if errors and os.environ.get("MY_HOOK_OVERRIDE"):
    return   # hook-internal escape, separate from --force-pattern-drift

What the CLI deliberately does not defend against

Malicious plugin code

A plugin can os.system("rm -rf $HOME"), exfiltrate ~/.aws/credentials, open a reverse shell, run a cryptominer, anything Python can do. The CLI doesn't sandbox plugin code because sandboxing Python is a hard problem with no clean OSS solution, and adding it would slow down development without buying real safety (a determined attacker fingers around any in-process sandbox).

Mitigation in your hands: vet packages before installing them. Pin to specific versions in your requirements.txt. Use a private PyPI mirror if your org doesn't want to allow arbitrary public PyPI installs.

Hung plugins

A plugin that does while True: pass blocks the CLI indefinitely. There is no per-plugin timeout.

Mitigation in your hands: plugins that make network calls should set per-request timeouts:

import requests
r = requests.get(url, timeout=5)  # bounded; won't hang forever

If you're authoring a plugin that could hang on a flaky external service, set a tight timeout and fall back to a pass result.

Resource exhaustion

A plugin can allocate unbounded memory, fork subprocesses, hold file descriptors. The CLI has no setrlimit calls or cgroup integration.

Mitigation in your hands: if you're running plugins in a deploy environment with limits, set them at the OS level (Docker --memory, Kubernetes resource requests, systemd MemoryMax). The CLI doesn't do this for you.

URL-embedded credential leaks

The redactor catches key=value assignments and provider-token shapes, but it doesn't currently match https://user:password@host URLs or HTTP Basic Auth headers in free-form exception text.

If your plugin handles HTTP responses with embedded creds, scrub them yourself before raising or logging.

How to think about plugin trust in your org

Three policies most orgs converge to:

Policy 1 — only allow plugins from your private index

# in ~/.pip/pip.conf or your org's standard pip config
[global]
index-url = https://pypi.internal.example.com/simple/
# Nothing comes from public PyPI; vetting happens at index admission.

Plugins are tested, scanned, and approved by your platform team before showing up in the internal index. Public PyPI is unreachable. Highest trust, highest friction.

Policy 2 — allowlist specific public PyPI plugins

# In your deploy environment's pyproject.toml or requirements:
data-product-forge==0.8.3
data-product-forge-sdk==0.9.0
data-product-forge-custom-scaffold==0.1.0
my-org-validators==1.2.0
# That's it. No other forge plugins.

Public PyPI is reachable but every plugin is on a tracked list. Medium trust, medium friction.

Policy 3 — trust the team running the CLI

If data-product-forge is run by a small data platform team on a hardened CI runner, pip install-anything is fine — same security boundary as the rest of your CI environment. Lowest friction, highest trust on the runner image.

The CLI doesn't care which policy you pick. It defends against the three failure modes above regardless.

What plugin authors should do

If you're publishing a plugin to public PyPI:

  • Pin your own dependencies with upper bounds. data-product-forge-sdk>=0.9,<1 not >=0.9.
  • Test on the supported Python matrix (3.10–3.14).
  • Document any non-stdlib dependency in your README — surprising network calls or filesystem access deserve a heads-up.
  • Scrub credentials before logging or raising. The CLI's redactor is a safety net, not your first line of defense.
  • Don't os.system-out unless it's literally what your plugin does. subprocess.run([...], shell=False) is safer than os.system(...).
  • Sign your wheels. PyPI Trusted Publishing + Sigstore make this free. Helps reviewers verify your package came from your CI, not from a typo-squatter.

If you're reviewing a plugin for org install:

  • Read the source. It's Python; it's small. The examples/ in the SDK repo are each <200 LOC.
  • Check the dependency graph. pip install --dry-run --no-deps <pkg> shows the immediate deps; pipdeptree shows transitive.
  • Look for os.system / subprocess / urllib / requests. Network calls and shell-outs are the highest-risk surfaces.
  • Run the plugin's tests. Confidence that the author tested their own claims.
  • Pin the version. Once vetted, write ==X.Y.Z in your install spec — and re-vet when bumping.

Reporting a plugin vulnerability

If a vulnerability lives in a third-party plugin (not in this CLI), file the report with that plugin's project.

If the issue is in how the CLI dispatches to plugins — the entry-point discovery, exception trapping, contract isolation, redaction logic — file it via the channel in SECURITY.md.

Source

  • Crash containment: fluid_build/cli/bootstrap.py, cli/validate.py, cli/apply.py
  • Contract deep-copy: search for copy.deepcopy in cli/apply.py::_run_apply_hooks
  • Redactor: fluid_build/observability/secret_redactor.py
  • Tests pinning all four guarantees: tests/test_cli_plugin_hooks.py
Edit this page on GitHub
Last Updated: 5/13/26, 6:01 AM
Contributors: fas89
Prev
Entry points reference
Next
Packaging