Skip to content

EdgeF-4/ai-act-kit

Repository files navigation

ai-act-kit

A self-hostable toolkit that gives a deployer everything the EU AI Act Article 50 transparency obligations actually need: signed, machine-readable provenance for AI outputs, the visible disclosures users have to see, and the governance trail that proves you did both. It runs on the Node standard library with no runtime dependencies, fully offline, and needs no API keys.

License: MIT Node 20+ Runtime dependencies: none

Why I built this

The Article 50 transparency duties apply from 2 August 2026, and they carry real money: Article 99(4) sets fines for non-compliance at up to EUR 15 million or, for a company, up to 3 percent of total worldwide annual turnover, whichever is higher. Yet most of the "AI Act compliance" tooling I looked at either hand-waves the hard part or pretends to solve a problem that is not actually solvable. This kit takes the opposite approach. It is honest about what Article 50 puts on a deployer versus a provider, honest about what content marking can and cannot do (especially for text), and it ships the parts that genuinely hold up.

This is engineering tooling, not legal advice. How you deploy it, your editorial processes, and the rest of your obligations under the Act are yours to get right, with qualified counsel where it matters.

What Article 50 requires, briefly

Article 50 of Regulation (EU) 2024/1689 (the EU AI Act) sets transparency duties that apply from 2 August 2026. The full plain-language breakdown is in docs/article-50.md. The short version:

  • 50(1): people must be told when they are interacting with an AI system (the chatbot interaction notice). This is a provider design duty, but a deployer is the one running the chatbot and has to make sure the notice is shown.
  • 50(2): providers must mark synthetic audio, image, video, and text so it is machine-readable and detectable as AI, "as far as is technically feasible." This embed-at-generation duty is on the model provider, not the deployer.
  • 50(4): deployers must disclose deepfakes (image, audio, video) and must disclose AI-generated text published to inform the public on matters of public interest. These are the deployer duties this kit is built around.
  • 50(5): the disclosure has to be clear, distinguishable, accessible, and given at the latest on first exposure.

What watermarking can and cannot do

This is the part most products get wrong, so I want to be direct about it.

For images, audio, and video, you can embed a cryptographically signed provenance manifest inside the file. This is what C2PA Content Credentials do, and the EU work on labelling names C2PA as an example approach. Removing or altering that manifest is detectable. This is robust and it is the right answer.

For text, there is no robust equivalent, and there is no honest way to claim otherwise:

  • Signed provenance carried with the text (a sidecar or an embedded token) is strong and tamper-evident whenever the manifest is present, but nothing stops someone from copying the words and dropping the manifest.
  • Statistical or steganographic text watermarks (token biasing, zero-width characters, and similar) are cheaply removed by paraphrasing, translating, or passing the text through any editor that normalizes characters. The published research is consistent that these are not robust.

So this kit does not pretend to make text self-marking in a way that survives a determined or even a casual strip. It includes an optional zero-width text watermark, but it is off by default and it is documented in the code and the tests as bypassable. There is literally a test that strips it to prove the point.

Why this design holds up

Because no single mechanism is sufficient for text, the defensible answer is to layer the mechanisms that are real and not lean on the one that is not:

  1. A signed C2PA-aligned provenance manifest. Robust and verifiable whenever it travels with the content.
  2. An embedded reference token, openly documented, that links copied content back to its manifest when the carrier (an HTTP header, an HTML meta tag, or a text footer) survives.
  3. The visible disclosure that Article 50(4) requires from deployers anyway, regardless of any cryptography.
  4. The optional zero-width watermark as a clearly-labelled, best-effort extra, never as proof.

On top of all four sits the governance layer: an audit log, an inventory, and an exportable compliance report, because being able to show what you did is itself part of what an enforcement review asks for.

Where it sits in your stack

You keep your model, your provider, and your publishing flow. The kit is the step between "the model produced something" and "we published it", plus the verify and reporting paths that hang off that step.

flowchart LR
    APP["Your app<br>produces AI output<br>(text, image, audio, video)"] --> KIT["ai-act-kit<br>SDK mark() or POST /api/mark"]
    KIT --> PUB["Published artifact<br>visible disclosure + reference token"]
    KIT --> SIDE["Signed sidecar manifest<br>(C2PA-aligned, Ed25519)"]
    KIT --> LOG["Append-only audit log"]
    PUB --> USERS["Your users<br>see the disclosure on first exposure"]
    PUB -. copied text can carry the token .-> VER
    SIDE --> VER["Anyone verifies offline<br>detect + verify, no secrets needed"]
    LOG --> REP["Inventory + compliance report<br>JSON or Markdown"]
    REP --> REV["What you hand a reviewer<br>dashboard or export"]
Loading

The internal layering, the manifest format, and the trust and threat model are in ARCHITECTURE.md.

Quickstart

Requirements: Node 20 or newer. There are no other runtime dependencies.

git clone https://github.com/EdgeF-4/ai-act-kit.git
cd ai-act-kit
npm install   # fetches TypeScript and the Node types, the only dev dependencies
npm test      # builds, then runs the suite

The test suite makes no network calls and needs no keys. It signs with ephemeral in-memory keys, so a marked output round-trips (mark, then detect, then verify) completely offline. The only step that touches the network is the one-time npm install of the build tooling, the same as any Node project.

Using the SDK

import { AiActKit } from "ai-act-kit";

// With no config and no key file, the kit uses safe defaults and an ephemeral
// signing key. For production, run `ai-act-kit keygen` and point config.json at
// the private key.
const kit = new AiActKit();

const result = kit.mark({
  content: "The committee approved the budget this morning.",
  contentType: "text",
  purpose: "public-interest-text",
  generator: { name: "your-model", version: "1.0" },
});

// result.markedContent is the published text: the visible disclosure label plus
// an embedded reference token. result.signedManifest is the sidecar you store.
console.log(result.markedContent);

// Later, anywhere, verify it:
const detected = kit.detect(result.markedContent!);
const verdict = kit.verify(detected!.content, result.signedManifest);
console.log(verdict.valid); // true

For media, the manifest binds the raw bytes and the kit returns a caption to render next to the file rather than mangling the bytes:

const img = kit.mark({
  content: pngBuffer,
  contentType: "image",
  purpose: "deepfake",
  generator: { name: "your-image-model" },
});
console.log(img.caption); // "This image, audio, or video was generated or altered by AI."
console.log(kit.verify(pngBuffer, img.signedManifest).valid); // true

Chatbot interaction notice (50(1))

A framework-agnostic middleware sets a notice header and exposes the text on res.locals. It depends on a minimal request and response shape, so it works with Express, Connect, and similar without taking a dependency on any of them.

import express from "express";
import { AiActKit, interactionNoticeMiddleware } from "ai-act-kit";

const kit = new AiActKit();
const app = express();
app.use(interactionNoticeMiddleware(kit.config));

HTTP API

Everything the SDK does for marking and verifying is also one HTTP call away, so a workflow tool or a non-Node service can integrate without touching TypeScript. Start the server and post to it:

curl -X POST http://localhost:8099/api/mark \
  -H 'content-type: application/json' \
  -d '{
    "content": "The committee approved the budget this morning.",
    "contentType": "text",
    "purpose": "public-interest-text",
    "generator": { "name": "your-model", "version": "1.0" }
  }'
Endpoint What it does
POST /api/mark mark content; returns the published text, the signed sidecar manifest, the reference token, and the audit record
POST /api/verify check content bytes against a signed manifest; body is { "content", "signedManifest" }
GET /api/health liveness, version, and whether the signer is ephemeral
GET /api/config the sanitized disclosure config, never key material or paths
GET /api/inventory aggregate view of everything marked
GET /api/audit?limit=N recent audit records
GET /api/report compliance report as JSON; ?format=md for Markdown

One detail worth knowing: verify checks the exact bytes the manifest binds. For text that is the boundContent field mark returned (the disclosed text without the carriers), not the full markedContent. The SDK's detect() recovers those bytes from a marked text by stripping the footer and the optional watermark.

Dashboard

npm run build
npm start            # serves http://localhost:8099

The dashboard is read-only over the audit log. It shows the inventory, the disclosure configuration, the recent marked outputs, and links to download the compliance report as Markdown or JSON. The API never exposes key material.

CLI

npm run build
node dist/src/cli.js version
node dist/src/cli.js keygen --out ./keys   # writes the private key with 0600

Docker

docker compose up --build   # dashboard on http://localhost:8099

The image is Node plus the compiled output, since there are no runtime dependencies. The audit log and reports persist in the mounted ./data directory. To use a stable signing key, create config.json from config.example.json, point it at your key, and uncomment the config mount in docker-compose.yml.

30-second integrations

Two worked examples live in examples/:

  • examples/n8n-workflow.json: an importable two-node workflow (a manual trigger and one HTTP Request node) that marks a piece of text through POST /api/mark. Import it, point the URL at your kit, run it, and the output is the published text plus the signed manifest.
  • examples/fastapi_app.py: a FastAPI service whose /publish endpoint sends AI-drafted text through the kit before returning it, and stores the sidecar manifest next to the response.

Both talk to the same POST /api/mark endpoint, so the pattern carries to any stack that can make an HTTP call.

Configuration

Copy config.example.json to config.json and edit it. The real config.json is gitignored and should be chmod 600. Everything has a safe default, so the kit runs with no configuration at all.

Key settings: the per-purpose disclosure labels and placement, the signing key paths, the governance data directory, an optional trust list of signer key ids, your documented implementation choices (which appear in the compliance report), and whether the optional watermark is on.

What this kit does not do

  • It does not generate content and does not embed marks at generation time. Under Article 50 the embed-at-generation marking duty in 50(2) sits with the model provider. This is the deployer-side toolkit.
  • It does not make text self-protecting. It makes provenance available and verifiable when present. Stripping a sidecar or a token from text is trivial and not prevented.
  • It does not implement the full C2PA binary embedding (JUMBF, COSE, CBOR) into media files. It produces a signed, C2PA-aligned manifest; for media you would hand that to the official C2PA toolkit to embed it in the file.
  • It is not legal advice and does not by itself make you compliant.

Project layout

src/
  c2pa/          manifest building, signing, verification, embedded references
  crypto/        Ed25519 key handling
  disclosure/    visible labels and the interaction-notice middleware
  governance/    audit log, inventory, compliance report
  watermark/     optional zero-width text watermark
  server/        dashboard and JSON API
  kit.ts         the AiActKit facade
test/            offline tests, including the mark/detect/verify round trip
docs/            the Article 50 grounding
examples/        30-second n8n and FastAPI integrations against the HTTP API

License

MIT. See LICENSE.

About

Self-hostable EU AI Act Article 50 transparency toolkit for deployers.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors