TIP Protocol

Changelog

2.1.6

  • Plugin-directory listing: complete readme rewrite to improve discoverability on WordPress.org search and external search engines. Sharper short description, new “Why publishers and creators use TIP Protocol” value-prop block, new “Who is this for” industry-targeting block, expanded FAQ with high-volume questions (AI-generated content handling, C2PA / Content Credentials comparison, multi-author and multisite workflows, security model, network endpoints), updated tag list (ai disclosure, content provenance, content authenticity, trust badge, publisher). No code changes.
  • Removed the local-development / Docker section from the user-facing readme. That content now lives only in the repository docs where it belongs; it was diluting the SEO signal of the public plugin page and listed local-development credentials that should not appear on a public listing.

2.1.5

  • Security: Restructured the byline metabox nonce check (admin/class-tip-metabox.php) into the canonical isset → wp_verify_nonce → current_user_can sequence, with each gate an independent early-return so the conditional logic cannot be bypassed. Sanitized $_POST input at the boundary with explicit wp_unslash + is_array check before the per-entry sanitize_text_field loop.
  • Security: Sanitized every $_SERVER access. includes/class-tip-webauthn.php and includes/class-tip-visitor-context.php now apply sanitize_text_field( wp_unslash( … ) ) in a single expression so the WordPress coding-standard analyser sees the sanitization chain, and the phpcs:ignore comments that previously masked these reads have been removed.
  • Security: Rewrote the legacy-slug admin redirect (admin/class-tip-admin.php redirect_legacy_menu_slug) to use an explicit allow-list of forwardable query params (tab, section, paged), each sanitized via sanitize_text_field and rawurlencoded before reaching add_query_arg. The previous raw $query_args = $_GET assignment is replaced. Capability check moved above all $_GET access so unauthorized requests never touch the superglobal.
  • Security: Added a defence-in-depth current_user_can( theailab_set_origin ) gate to Theailab_Admin::build_editor_state() before reading $_GET[‘post’]; absint() now wraps the post-ID lookup explicitly.
  • Prefix uniqueness: Removed the legacy un-prefixed shortcode registrations ([tip-badge], [download_button]) and the legacy register_block_type call for tip/verification-badge. Replaced the alias approach with a the_content filter (Theailab_Public::rewrite_legacy_tokens) that rewrites legacy tokens to their prefixed names at render time, so existing post content authored against earlier versions keeps rendering after upgrade. Updated the Gutenberg JS to register theailab/verification-badge, theailab-origin-panel, and theailab-pre-publish-check (previously tip/verification-badge, tip-origin-panel, tip-pre-publish-check).
  • No functional changes for end users.

2.1.4

  • Compatibility: Bump “Tested up to” header to 7.0 (current WordPress release). No code changes; the WordPress.org automated scanner blocks uploads whose declared compatibility falls below the latest WP version, and this metadata bump unblocks the resubmission of the 2.1.3 prefix-uniqueness fixes.

2.1.3

  • Compliance: Address WordPress.org Plugin Directory review feedback on prefix uniqueness. Three PHP-level identifiers carried short or generic prefixes that could collide with other plugins:
    • Bootstrap function tip_protocol() renamed to theailab_tip_bootstrap(). A guarded backward-compat alias preserves the old name for any third-party code or theme snippet that referenced it.
    • Gutenberg block namespace tip/verification-badge renamed to theailab/verification-badge. The legacy block name is also re-registered as an alias so existing post content that embeds the old block continues to render unchanged.
    • Admin menu slug tip-protocol (previously routed to admin.php?page=tip-protocol) was already moved to theailab in an earlier release; this version adds a 301 redirect from the legacy URL to the new one so bookmarked admin URLs keep working.
    • Shortcodes [tip-badge] and [download_button] continue to render via backward-compat aliases registered alongside the canonical [theailab-tip-badge] and [theailab_download_button] names.
  • No functional changes for end users. Existing content keeps working; the renames are purely to satisfy the WordPress.org plugin uniqueness rule.
  • Bump version forces a cache-buster URL refresh.

2.1.2

  • UI fix: The scope picker dropdown clipped the descenders of “publishers” because the wp-components SelectControl forced a fixed height that conflicted with the picker’s padding. Replaced the height constraint with height: auto, increased line-height to 1.5, switched to a custom-drawn navy chevron with explicit right-padding so the option text isn’t crowded, and bumped vertical padding to 12px. The dropdown now renders the full option text cleanly at any zoom level.
  • Sign reliability: Added a server-side self-verification step right after each ML-DSA signature is produced and before the registration request is sent to the TIP node. If the stored public key doesn’t correspond to the private key the plugin just signed with — most often because a personal Author ID was imported with a derived (not original) public key — the user gets a precise local error pointing at the cause and remediation, instead of the opaque “Content signature verification failed” the node returns.
  • Sign reliability: The registration payload sent to the TIP node now includes the public_key field. Without it, the node had to look up the TIP-ID’s public key on the DAG to verify, which fails for newly-imported personal Author IDs that haven’t been published to the network yet. Including the key gives the node a deterministic verification path; the node should still cross-check this against its DAG record when one exists.
  • Sign reliability: When the TIP node rejects a registration with a “signature verification” error AND the local self-verify passed, the editor now surfaces a guided message explaining that the TIP-ID likely isn’t yet published to the TIP DAG (and pointing the user at vp.theailab.org), instead of relaying the raw node error verbatim.
  • Bump version forces a cache-buster URL refresh.

2.1.1

  • UI: The “What kind of identity is this?” dropdown on the Connect-an-Identity card was easy to overlook even though it controls the most important decision in the import flow (publisher vs. personal). Wrapped it in a high-contrast picker with a colored border, gradient background, “REQUIRED” tag, large icon (🏢 / 👤), and a prominent uppercase label. The picker color-shifts to blue when “Site identity” is selected and green when “Personal Author ID” is selected, so the user gets instant visual confirmation that their choice registered.
  • Bump version forces a cache-buster URL refresh.

2.1.0

  • Feature: Optional WebAuthn / biometric step-up for personal-mode signing. Writers who choose to can now require a fingerprint, Face ID, Windows Hello, or hardware security-key tap before each post is signed with their personal Author TIP-ID. Configured per-user in WordPress profile, defaults OFF for everyone, and is never a barrier — publisher-mode signing, scheduled posts, WP-CLI, REST automation, and cron-driven workflows always bypass step-up. If a user enables the toggle but has no enrolled key, signing still proceeds; if they remove their last key, the toggle auto-disables.
  • New REST surface under /theailab/v1/webauthn/ (status, register/begin, register/complete, auth/begin, auth/complete, required, credentials/remove). All routes are nonce-protected and operate only on the calling user’s row.
  • Server-side verifier uses native PHP openssl_verify() with public keys exported directly from the browser via AuthenticatorAttestationResponse.getPublicKey() — no third-party dependency, no CBOR parser, supports ES256 and RS256 algorithms, enforces userVerified flag and signCount monotonic check.
  • Editor integration: Both classic and Gutenberg editors detect a 428 Precondition-Required response from /origin/register, run the WebAuthn assertion ceremony, and retry with the resulting single-use ticket. Errors are surfaced via the existing inline-status banner with clear remediation guidance.
  • Security audit: WebAuthn step-up is a defense-in-depth layer over the existing capability/nonce model — it gates the API call but does not replace any existing check, and the actual content signature remains ML-DSA-65 over the canonical hash, server-side, exactly as before.

2.0.11

  • Fix: When the TIP node returned a structured error response (e.g. {“error”:{“message”:”…”}}), the plugin’s API client cast the nested object to a string and surfaced the literal word “Array” in the editor — users saw “Couldn’t sign this post / Array” with no actual reason. The client now walks the common shapes (error as string or {message,detail,description,code,reason,details[]}, top-level message, plural errors[]) and falls back to a short raw body snippet or a status-code message, so the real reason from the node always reaches the user.
  • Bump version forces a cache-buster URL refresh.

2.0.10

  • Fix: Personal Author IDs exported from vp.theailab.org (DOB-locked tip-key-export-v2 files) failed to import with TIP-PKG-2 packages must declare vp_id.. Personal identities are self-claimed and never carry a publisher review, so the importer no longer requires vp_id for tip_id_type === “personal”. Publisher and platform identities continue to require a well-formed vp_id, and personal exports that do include one are still validated against THEAILAB_VP_ID_REGEX.
  • Bump version forces a cache-buster URL refresh.

2.0.9

  • Apply the v2.0.8 inline status banner pattern to every action button across the admin UI: Connect identity, Inspect article, Verify domain (HTTP / DNS), Save settings (Network / Publishing defaults), Add/Save writer to editorial team, Refresh roster, classic-editor “Register Content as …” button, and the Gutenberg sidebar Sign button. Errors and successes now appear as prominent red / green banners with icon + title + detail, immediately at the click site, instead of as small notices at the top of cards the user has scrolled past.
  • New reusable InlineStatus React component centralizes the banner so all panels share one look and one set of accessibility semantics (role=”alert” for errors with aria-live=”assertive”, role=”status” for successes with aria-live=”polite”).
  • Classic-editor metabox: status banner moved from below the Register button to above it (the user’s eye is on the button), and upgraded from plain text to the same banner style.
  • Bump version forces a cache-buster URL refresh.

2.0.8

  • Fix two issues that surfaced after the v2.0.7 DOB-unlock improvements: (1) the synthetic post-unlock package shipped with package_version: “tip-key-export-v2-unlocked”, which the importer rejected with Unsupported .tip.json package_version. Synthetic packages now use the canonical TIP-PKG-2 version string. (2) The import error notice rendered as a small banner at the top of the import card, far from the Connect button the user just clicked, so it was easy to miss. The error / success message now renders as a prominent red (or green) banner immediately above the Connect button with an icon, title, and dismiss button.
  • Bump version forces a cache-buster URL refresh.

2.0.7

  • Fix import for .tip.json files where publicKey is empty in the clear (a known property of vp.theailab.org’s tip-key-export-v2 exports when the user’s record had no stored public key at export time). The server-side /identity/import endpoint now derives the public key from the private key via the bundled bin/tip-ml-dsa.mjs helper (action: derive-public) before passing the package to the key manager. The derived key is injected at top-level public_key and into identity.public_key (mirroring the original path) so downstream parsing succeeds whether the importer reads from one or the other.
  • Loosen the client-side dropzone parser to accept files with an empty public_key value as long as a private_key is present. The Connect button is no longer disabled by this condition; the server completes the derivation during import.
  • Bump version forces a cache-buster URL refresh.

2.0.6

  • Fix DOB unlock for tip-key-export-v2 files. After reading the actual producer code at vp.theailab.org (settings.html + src/crypto.js), the format is now pinned exactly: byte layout [salt:16][iv:12][ct+gcm-tag], PBKDF2-SHA-256 with 200,000 iterations, AES-256-GCM, password is the user’s date of birth normalized to 8 digits in MMDDYYYY order, and the plaintext is the raw private-key hex string (not a JSON wrapper). Previous fallback-by-trying-many-layouts code is replaced with this single deterministic decryption. Wrong DOB now produces a clear “That date of birth doesn’t unlock this file” message.
  • Replace the <input type=”date”> (which forced YYYY-MM-DD) with a <input type=”text” placeholder=”MM/DD/YYYY”> that auto-inserts slashes as the user types — same behavior as the producer’s input field. The Unlock button stays disabled until exactly 8 digits are entered.
  • Decrypted private-key hex is wrapped in a synthetic parser-friendly package using the tip_id and publicKey fields from the outer file; the existing parser then takes over and unlocks the Connect button as it would for any .tip.json file.
  • Gutenberg sidebar: panel renamed from “TIP Origin” to “TIP Protocol”. Field label changed from “Origin code” to “How was this written? Human or AI”. Option labels (OH – Original Human / AA – AI-Assisted / AG – AI-Generated / MX – Mixed) and the Register button label remain unchanged. The “Next update type” field is moved into a collapsible “Advanced options” disclosure (collapsed by default) so writers see only the question that matters.
  • Bump version forces a cache-buster URL refresh.

2.0.5

  • Add support for the new tip-key-export-v2 personal Author ID file format from vp.theailab.org. These files are encrypted with the user’s date of birth using PBKDF2 (SHA-256, 200,000 iterations) + AES-256-GCM. The plugin now detects the locked format, shows a dedicated “This file is locked” card with a date-of-birth input, decrypts the file client-side via Web Crypto API, and hands the unwrapped public_key + private_key to the existing import flow. The DOB never leaves the browser. Site identity files (publishers) continue to use the existing flat / identity.* format with no DOB step.
  • Replace the muted yellow warning notice with a prominent RED error banner for hard parse failures (invalid JSON, missing required fields, encrypted blob without unlock support). The error appears immediately under the dropzone with a red icon, red title, and the diagnostic detail.
  • Bump version forces a cache-buster URL refresh.

2.0.4

  • Connect button stayed disabled because the .tip.json parser only checked 6 hard-coded paths for the public key and private key. Expanded the parser to recognize 21 paths each (top-level snake/camel/lowercase variants, identity.*, keys.*, data.*, key_package.* wrappers). Now handles TIP-PKG-1, TIP-PKG-2, vp.theailab.org exports, AI-Lab-issued packages, and reasonable variants of all of the above.
  • When a required field is missing, the error message now lists the fields the parser actually detected in the file, instead of just saying “field X missing”. This makes it possible to diagnose schema mismatches without opening DevTools.
  • Bump version forces a cache-buster URL refresh.

2.0.3

  • Fix Connect button not working after .tip.json upload. The double-prompt confirmation modal was using wp.components.Modal, which can render as undefined in certain wp-components versions and prevent the entire panel from re-rendering. Replaced with a self-contained <div> overlay using pure CSS (no wp-components dependency), so the modal always renders regardless of host WordPress version.
  • The modal still supports clicking the backdrop to dismiss, an explicit close button, ARIA dialog semantics, and the same content (file name, detected Author ID, identity type, scope-mismatch warning).
  • Bump version forces a cache-buster URL refresh.

2.0.2

  • Fix REST nonce parameter mismatch that broke every nonce-protected admin call (identity import, identity remove, contributor CRUD, byline save, origin save, “Register Content” button, settings save, domain verify). The PHP validator was renamed to read theailab_nonce in v1.5.0 but the JavaScript continued to send tip_nonce. Renamed the parameter on the JS side in admin/js/tip-admin.js, admin/js/tip-gutenberg.js, and admin/js/tip-origin-classic.js.
  • Add tests/test-rest-nonce-consistency.php (10 assertions) so this regression cannot recur silently. Test suite is now 344 assertions across 11 standalone files.
  • Add a double-prompt confirmation modal before the .tip.json import API call fires. The modal shows the file name, the detected Author ID and identity type from the file, the scope the user selected (Site identity vs Personal Author ID), and a plain-language explanation of what each scope does. When the file’s identity type does not match the chosen scope (e.g., a personal-typed file imported as a site identity, or a publisher-typed file imported as personal), the modal displays a warning notice explaining what the mismatch will do. The user must explicitly confirm before the import proceeds.
  • Bump version (also forces cache-buster URL refresh for any browsers still holding the broken 2.0.1 JS).

2.0.1

  • Address WordPress.org plugin review feedback on prefix uniqueness:
    • Remove legacy unprefixed shortcode aliases [tip-badge] and [download_button]. The canonical shortcodes [theailab-tip-badge] and [theailab_download_button] continue to work.
    • Rename admin menu slug from tip-protocol to theailab (admin URL is now wp-admin/admin.php?page=theailab).
    • Rename the screen-hook check accordingly.
    • Rename WordPress script and style handles from tip-protocol-* to theailab-* (theailab-admin, theailab-editor, theailab-origin-classic, theailab-gutenberg, theailab-public, theailab-badge).
    • Rename Privacy Exporter / Eraser registration key from tip-protocol-identity to theailab-identity.
    • Rename admin app DOM root id from tip-protocol-admin-root to theailab-admin-root.
    • Rename classic-editor metabox ID, form-field IDs, and name attributes from tip-origin-* and tip_origin_* to theailab-origin-* and theailab_origin_*.
  • Verify all REST permission_callbacks use current_user_can() with prefixed custom capabilities (theailab_manage_settings, theailab_register_content, theailab_set_origin, theailab_view_dashboard).
  • No functional changes: the protocol, signing, byline picker, contributor registry, and CNA-2.2 wire format are unchanged.

2.0.0

  • Add Contributor Registry: an org-scoped registry of personal TIP-IDs (journalists, editors, photographers, freelancers) under a publisher / platform identity. Manage from Settings → Contributors. Each contributor record carries a TIP-ID, display name, role, optional WordPress user link, key-custody mode (attribution-only or self-signing), and optional public key.
  • Add per-post byline picker: a new section in the classic-editor metabox and the Gutenberg sidebar lets editors choose a primary author plus optional co-authors from the contributor registry. The selection is persisted as ordered post meta _theailab_author_tip_ids.
  • Auto-fill the byline from the WordPress post-author when that user has a contributor-registry mapping. Editors can override.
  • Bump canonical normalization to CNA-2.2: the publisher-signed payload now carries an ordered authors[] array (primary at index 0) with per-author key_mode and signed flags. Solo-author posts and authorless hosted content remain supported.
  • Add CNA-2.2 crypto helpers: build_cna_2_2_payload(), sign_publisher_v3(), verify_publisher_v3(), sign_co_signer(), verify_co_signer(). CNA-2.1 verification preserved for backward compatibility (THEAILAB_CNA_VERSION_PREVIOUS).
  • Add co_signatures[] collection: when a contributor is in self-signing mode, the registrar records a pending co-signature placeholder so the node can ingest the contributor’s own ML-DSA-65 signature out-of-band.
  • Add REST routes:
    • GET/POST /theailab/v1/contributors — list or add a contributor
    • PATCH/DELETE /theailab/v1/contributors/{tip_id} — update or remove
    • GET/POST /theailab/v1/post-authors/{post_id} — read or set the byline
  • Extend public output: schema.org JSON-LD author becomes an array of Person objects when multiple authors are bound; new meta tags tip:co-authors and tip:authors-count.
  • Add new tests: test-contributor-registry.php (55 assertions), test-cna-2-2-payload.php (10 assertions covering canonical determinism + alphabetical key ordering with authors[]).
  • Register _theailab_author_tip_ids post meta with REST schema so the Gutenberg sidebar can read/write it through core/editor.
  • Storage: new option theailab_contributors (autoload no), new user meta theailab_contributor_tip_id (back-pointer for fast WP-user → contributor lookup).

1.5.0

  • Rename all plugin-internal identifiers from tip_ / TIP_ to theailab_ / THEAILAB_ to comply with WordPress.org plugin guideline on prefix uniqueness (4+ char distinct prefix). Affects classes, constants, functions, options, capabilities, post meta keys, user meta keys, cron events, transients, REST namespace, and database tables.
  • Add automatic one-time migration in the activator: copies legacy tip_* options, capabilities, post meta, user meta to the new theailab_* keys; renames wp_tip_content → wp_theailab_content and wp_tip_transactions → wp_theailab_transactions; unschedules legacy cron events.
  • Register backward-compatible shortcode aliases: [tip-badge] continues to render alongside the new canonical [theailab-tip-badge].
  • Sanitize $_SERVER reads with sanitize_text_field immediately after wp_unslash to satisfy the WordPress coding standard’s “sanitize early” rule for superglobals.
  • Rename WP_Error codes from the legacy prefix to the canonical prefix.
  • Preserve TIP Protocol JSON wire-format field names (tip_id, tip_id_type, tip_ids, tip_key) which are protocol-spec values defined in the patent and not plugin-internal identifiers.

1.4.0

  • Add typed identity taxonomy (personal, publisher, platform) with reserved values for future Phase 2 release
  • Add relational attribution_mode field (self, employed, hosted) carried in CNA-2.1 canonical content payload
  • Add Signing Officer roster awareness with central genesis-node policy (disabled / optional / required); Phase 1 default is optional so manual publisher onboarding works without a roster
  • Add per-post edit_post / read_post permission checks to /tip/v1/register, /update, /status, /origin/save, /origin/register
  • Move temporary crypto scratch files from get_temp_dir() to wp-content/uploads/tip-protocol-private/ with .htaccess, web.config, and index.php deny files plus chmod 0600/0700
  • Drop JSON_UNESCAPED_SLASHES from application/tip+json and application/ld+json script-tag output to prevent </script> break-out
  • Fix vendored @noble/post-quantum ML-DSA-65 imports so the post-quantum signing helper resolves dependencies without node_modules
  • Add identity removal: REST endpoint /tip/v1/identity/remove plus admin UI controls for both Publisher and Creator identities, with typed-confirmation guard for the destructive Publisher case
  • Add fluid typography across the admin UI; new 560px mobile breakpoint; :focus-visible, prefers-reduced-motion, and prefers-contrast support; WCAG 2.5.5 minimum touch-target sizing
  • Remove load_plugin_textdomain() (auto-loaded by WordPress 4.6+ for directory-hosted plugins)
  • Bump Contributors to include theailaborg

1.3.0

  • Align WordPress registration hashing with the current developer hashing guide
  • Build the content hash input from a structured WordPress content string instead of raw rendered body text alone
  • Add guide-compliant body cleaning with tipmediaimage, tipmediavideo, and tipmediaaudio indicators
  • Add CNA-MIX-1 top-level content hashing when attached media participates in the registration hash
  • Store and expose text_canonical_hash plus attached-media hash fields in the local record and public TIP manifest
  • Add test coverage for content-string ordering, guide body cleaning, and mixed-hash composition

1.2.3

  • Change TIP identity setup to import-only onboarding for reviewed publisher and VP creator identities
  • Add .tip.json package import support and clearer rejection for browser-connected WebAuthn blobs
  • Add local sign-and-verify checks for imported key material before WordPress stores it
  • Upgrade key-at-rest encryption to a TIP-ID-specific SHAKE-256 plus PBKDF2 AES-256-GCM payload while keeping backward decryption support
  • Emit the canonical registered_url directly in X-TIP-Content-Bind

1.0.2

  • Add richer fallback schema and merge TIP provenance into Yoast SEO and Rank Math graphs without duplicating article JSON-LD
  • Improve badge and public trust UI accessibility with stronger semantics, focus handling, and keyboard-safe details
  • Add privacy-policy guidance plus personal data exporter and eraser integration for stored user TIP identity data
  • Bundle the official TIP mark locally for admin branding and WordPress.org packaging
  • Default the public badge script to the bundled local copy and add release packaging hardening files

1.0.1

  • Simplify plugin branding to TIP Protocol with Publisher Provenance for WP as the subtitle
  • Align content hashing and verification badges with the CNA-2 registration spec

1.0.0

  • Initial release

Plugin Website
Visit website

Author
The AI Lab
Version:
2.1.7
Last Updated
June 3, 2026
Requires
WordPress 6.0
Tested Up To
WordPress 7.0
Requires PHP
8.1

Share Post

Join our newsletter.

Get insights into what’s happening at ChangelogWP right in your inbox. We don’t believe in spam.