Skip to main content

Map and govern where your AI data flows — and who can compel its disclosure.

Project description

borderlint

GitHub Marketplace

Map and govern where your AI data and traffic flow — and who can compel its disclosure.

A static, in-CI linter for AI data residency and sovereignty across APAC & EMEA, with first-class HK / GBA support. borderlint statically scans your repo (Python and TypeScript/JavaScript) for AI provider usage and evaluates each flow against two orthogonal dimensions:

  • Residencywhere the bytes rest. Each flow resolves to a jurisdiction (ccTLD/ISO codes plus the CN-GBA / GBA tokens); a flow outside the allow-list for the declared data class fails the build.
  • Sovereigntywhich government can compel disclosure. A US-headquartered provider (AWS, Azure, GCP, OpenAI, Anthropic) is subject to the CLOUD Act regardless of the endpoint region; a flow to AWS Bedrock ap-east-1 is residency-clean for a PDPO policy (residency hk) yet remains under US sovereignty. An optional sovereignty block constrains this per class.

Declare a home base — HK, Macao, the GBA, Japan, Korea, Singapore, Australia, the UK, the EU, or Malaysia — and flagged flows are tagged with the matching regime (PDPO, PIPL, APPI, PIPA, PDPA, the Privacy Act, GDPR …) and its cross-border reference. Western and Chinese providers are treated evenly. Zero runtime dependencies.

Use

python -m borderlint scan ./service --policy residency.json --classification customer-pii
  • No --policyinventory mode (lists flows + jurisdictions, exits 0).
  • --format json|mermaid|sarif|sbom — machine output, a flow map, SARIF for GitHub code-scanning, or a deterministic AI data-flow SBOM (policy-independent inventory of every flow; an artifact, exits 0).
  • diff <baseline.sbom> <current.sbom> — compare two SBOMs; exits 1 when the PR adds a new non-local flow (new egress), else 0. Diff the base-branch SBOM against the PR's to gate new AI egress.
  • Accept a reviewed flow with an inline # borderlint: allow <reason> waiver (justification required; it's reported as waived, not hidden, and can't override an explicit provider deny).
  • Exit code is non-zero on a violation, so it gates CI.

Policy (the eval-set)

residency.json maps each data class to the jurisdictions you accept, and optionally to the sovereignty blocs you accept for compelled-disclosure exposure:

{
  "home_location": "hk",
  "classifications": {
    "customer-pii": ["hk", "CN-GBA", "sg"],
    "employee-pii": ["hk", "CN-GBA"],
    "non-pii":      ["hk", "CN-GBA", "cn", "mo", "sg", "us", "gb"]
  },
  "sovereignty": {
    "on_unknown": "warn",
    "classifications": { "customer-pii": ["eu", "uk", "local"] }
  }
}

Residency — deny-by-default: a flow to any code not on the list for the declared class fails — so sg is allowed but my is not, matching a PDPO agreed-locations EULA. GBA is shorthand for hk + CN-GBA.

Sovereignty — opt-in, orthogonal to residency. Residency says where the bytes rest; sovereignty says which government can compel disclosure — a US provider (AWS, Azure, GCP, OpenAI) is subject to the CLOUD Act regardless of the endpoint region. Add a sovereignty block to constrain it per class. Bloc vocabulary: us, eu, cn, uk, ru, in, il, local, unknown. Absent the block, behaviour is unchanged (sovereignty is reported as a column but never gates). local sovereignty is exempt (self-hosted = no external sovereign). See CAPABILITIES.md §3.1 for the full model.

Declare your home_location — a GBA seat (hk/mo/CN-GBA) or an APAC/EMEA seat (jp, kr, sg, au, uk, eu, my) — and a flagged flow is tagged with the data-protection regime in play and linked to the relevant cross-border arrangement (the matching GBA Standard Contract variant, PIPL cross-border, GDPR, the UK IDTA, APPI Art. 28, PIPA Art. 28-8, PDPA s.26/s.129, APP 8) as reference links. (home_regime pdpo/pipl is still accepted.)

Capabilities

  • Languages: Python (AST) and TypeScript/JavaScript (import / require / dynamic import()), plus endpoint references in config/text files and OpenAI-compatible /v1/chat/completions calls — even to a runtime-configured host (resolved to unknown, so on_unknown: fail gates it).
  • Providers: 80+ across the east-west boundary — OpenAI, Anthropic, Google (Gemini + Vertex AI), Azure, Bedrock, Mistral, Cohere, Groq, Together, Perplexity, xAI, Cerebras, Fireworks, Replicate, SambaNova, Meta Llama + Tencent, Alibaba, DeepSeek, Moonshot, Zhipu/Z.ai, Baidu, Volcengine, MiniMax, plus AI21 (IL), Jina (DE), Voyage, GigaChat (RU), Sarvam (IN), Scaleway & OVHcloud (FR/EU) and region-selectable clouds (IBM watsonx, Oracle OCI, Cloudflare Workers AI, Herokuunknown until you pin a region) — with Python and JS/TS package names and the Vercel AI SDK (@ai-sdk/*).
  • Image / video / speech: generation (Stability AI, Black Forest Labs/Flux, Runway, Recraft) and speech-to-text / TTS (ElevenLabs, Deepgram, AssemblyAI, Soniox, Amazon Polly) — tagged with their category and governed for residency like any other flow.
  • Vector stores (data sinks): Pinecone, Weaviate Cloud, Qdrant Cloud, Zilliz/Milvus — flagged as vector_store and governed for residency (region is per-cluster, so default unknown).
  • Aggregators / routers: litellm, langchain, LlamaIndex, aisuite, OpenRouter, AI/ML API, Vercel AI core & Gateway → unknown (runtime-routed), so on_unknown: fail blocks them for sensitive classes.
  • Jurisdictions: ccTLD/ISO codes + CN-GBA / GBA; AWS / Azure / GCP-Vertex region resolved from the endpoint host where present (e.g. bedrock-runtime.ap-east-1… and asia-east2-aiplatform.googleapis.comhk).
  • Sovereignty: a per-flow bloc (us, eu, cn, uk, ru, in, il, local, unknown) derived from the provider's home legal regime — orthogonal to residency. Opt-in policy block; reported in every output format; host-level overrides for ring-fenced subsidiaries (e.g. AWS China / Sinnet → cn).
  • Policy: classification-keyed JSON eval-set, deny-by-default, provider allow/deny, configurable failure set, declared home regime.
  • Regimes & arrangements: declared home location → data-protection regime tag + the cross-border mechanism reference for a flagged flow (context only, never adjudicated). GBA seats hk/mo/CN-GBA → PDPO / Macao PDPA / PIPL + the matching GBA Standard Contract; APAC/EMEA seats jp (APPI), kr (PIPA), sg/my (PDPA s.26 / s.129), au (APP 8), uk (UK IDTA), eu (GDPR) → their transfer mechanism. PIPL cross-border and GDPR are also surfaced for those destinations.
  • Output & CI: text / JSON / Mermaid / SARIF / SBOM, an SBOM diff gate for new egress, inline waivers, exit codes, GitHub Action + Jenkins.

Scope

For HK / CN / GBA / MO plus JP / KR / SG / AU / UK / EU / MY home bases (regime tags + cross-border references). Not yet: AE / IN / ID (cross-border instruments not yet operational); other jurisdictions; CycloneDX / SPDX SBOM export and optional LLM enrichment. Per-capability status — shipped vs. next vs. later — is tracked in CAPABILITIES.md.

Internal endpoints

Map your own regional endpoints to jurisdictions; they merge with the bundled provider KB (your entries win on conflict):

{ "endpoints": { "llm-cn.acme.internal": "cn", "llm-hk.acme.internal": "hk", "llm-sg.acme.internal": "sg" } }
borderlint scan . --providers internal-endpoints.json --policy residency.json --classification customer-pii

A configuration wired to the wrong regional endpoint — e.g. the CN endpoint for HK-only customer PII — then fails the build, so you can't ship a service pointed at the wrong region.

A runnable end-to-end example is in examples/gba-resident-app/ — a GBA-resident app (internal Shenzhen endpoint → CN-GBA, plus Mainland / Western / local fallbacks). Run it under residency-hk.json vs residency-mo.json and the surfaced GBA Standard Contract flips between the (Mainland, Hong Kong) and (Mainland, Macao) variant, and the regime tag between PDPO and Macao PDPA:

borderlint scan examples/gba-resident-app \
  --providers examples/gba-resident-app/internal-endpoints.json \
  --policy examples/gba-resident-app/residency-hk.json --classification customer-pii

The same scan renders to a data-flow map grouped by jurisdiction — Mermaid source in dataflow.mmd, rendered to PNG:

borderlint AI data-flow map for the GBA-resident sample app, grouped by jurisdiction

CI

Same command in any pipeline. GitHub Actions (composite action):

- uses: iolairus/borderlint@v1.1.4
  with: { path: ., policy: residency.json, classification: customer-pii }

Jenkins / anything else: pip install borderlint && borderlint scan . --policy residency.json --classification customer-pii — a non-zero exit fails the stage. Full examples in examples/ci/.

pre-commit — catch a bad flow before it's committed (.pre-commit-config.yaml):

- repo: https://github.com/iolairus/borderlint
  rev: v1.1.4
  hooks:
    - id: borderlint
      args: [--policy, residency.json, --classification, customer-pii]

The hook runs borderlint scan over the repo (the args are required for a real gate; without a policy it runs inventory mode and always passes).

Keeping the KB fresh

A weekly GitHub Action (.github/workflows/kb-refresh.yml) diffs the bundled provider KB against litellm's registry and opens an issue listing providers we don't yet cover — jurisdictions are assigned by hand, never auto-merged. borderlint --version shows the KB's last-reviewed date. To add or correct a provider, see CONTRIBUTING.md (KB schema + PR workflow).

Development

borderlint is built spec-first with OpenSpec: every change is a reviewed proposal (specs + design + tasks) gated by a spec-reviewer agent before any code is written. To bootstrap the same workflow into another repo:

scripts/opsx-init.sh [--no-jira] /path/to/your/repo

It scaffolds AGENTS.md, .claude/ (slash commands + the spec-reviewer gate), an empty openspec/, and workflow.yaml. --no-jira trims it to the core loop — propose → review → apply → commit → ship.

License

MIT © 2026 Iolaire McKinnon. Vendor-neutral by design.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

borderlint-1.2.0.tar.gz (259.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

borderlint-1.2.0-py3-none-any.whl (31.8 kB view details)

Uploaded Python 3

File details

Details for the file borderlint-1.2.0.tar.gz.

File metadata

  • Download URL: borderlint-1.2.0.tar.gz
  • Upload date:
  • Size: 259.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for borderlint-1.2.0.tar.gz
Algorithm Hash digest
SHA256 a349acefd26d24bd7ddc465a5dfd8bc46d10ae7359f0dea771a7907ab9aba9c4
MD5 c4116c496efe96a501b891f0c9db51f9
BLAKE2b-256 ca659aadf9b99387d6c1b6fb030089c82c01c4f3151008d85d60d6ca6dbd28c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for borderlint-1.2.0.tar.gz:

Publisher: release.yml on iolairus/borderlint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file borderlint-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: borderlint-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 31.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for borderlint-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d45cf52807e2df3da589a7e5920fa7f23b7d8bacf5f479217a79d8e25fe06d11
MD5 98c70b067c9a13e976820a603b90521b
BLAKE2b-256 a979f1d63cf59869bae10337d88e77bd1dbd1605db36c60b0ad63fb5f0c19e7e

See more details on using hashes here.

Provenance

The following attestation bundles were made for borderlint-1.2.0-py3-none-any.whl:

Publisher: release.yml on iolairus/borderlint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page