Skip to content

Deploy fingerprint rules from client study - #28

Open
alltheseas wants to merge 2 commits into
mainfrom
deploy-fingerprint-rules
Open

Deploy fingerprint rules from client study#28
alltheseas wants to merge 2 commits into
mainfrom
deploy-fingerprint-rules

Conversation

@alltheseas

@alltheseas alltheseas commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace 7 original fingerprint rules with 14 validated rules from the client fingerprinting study
  • Remove broken content_pattern rules that matched mentions, not event origin
  • Fix case-sensitive client tag matching (Damus, Amethyst, Snort)
  • Add tag_name_present field for proxy tag detection (bridge traffic)
  • Add bot/service attribution: momostr, lnp2pbot, Ditto Trends, bitbot
  • Add Primal platform-specific rules (5 variants)
  • Add disagreement logging when fingerprint contradicts Tier 1 client tag
  • Add dev-pubkeys.json (informational reference, not used in matching)

Impact

All 14 rules achieve ≥99.9% precision against ground truth. Combined:

  • +32,174 new attributions (attribution rate 29.1% → 34.7%)
  • +28,472 invalid events attributed (57.3% of invalid unattributed)

Test plan

  • npm run build — TypeScript compiles cleanly
  • CI scan run with new rules — verify no regressions
  • Spot-check: disagreement logging doesn't fire on correct matches
  • Spot-check: Bridge traffic rule loads and matches proxy-tagged events

Closes #27

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved app detection using tag-based matching and tag-presence checks.
    • Added detection for: Bridge traffic, momostr, lnp2pbot, Ditto Trends, and bitbot.
  • Chores

    • Added developer public-key reference data for core applications.

Replace 7 original rules (4 broken content_pattern, 2 wrong case) with
14 validated rules achieving ≥99.9% precision. Key changes:

- Remove content_pattern rules (Primal, Snort, Coracle, Nostrudel) that
  matched mentions not origin
- Fix case-sensitive matching (damus→Damus, amethyst→Amethyst)
- Rename Mostr→"Bridge traffic" (proxy tag matches all bridges)
- Add bot/service rules: momostr, lnp2pbot, Ditto Trends, bitbot
- Add Primal platform-specific rules (Android/Web/iOS/Desktop/iOS Native)
- Add tag_name_present matching for proxy tag detection
- Add disagreement logging: warn when fingerprint contradicts client tag
- Add dev-pubkeys.json (informational, not used for matching)

Study results: 13 rules recover 57.3% of invalid unattributed events
(28,472 of 49,696) with 32,174 total new attributions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32f9d687-abd6-42cc-b1a1-7b8420314395

📥 Commits

Reviewing files that changed from the base of the PR and between 8e44038 and 5c79a48.

📒 Files selected for processing (1)
  • data/app-fingerprints.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • data/app-fingerprints.json

📝 Walkthrough

Walkthrough

Replaces several content-pattern fingerprints with tag-based rules, adds a developer pubkeys data file, extends the fingerprint schema with tag_name_present, updates matching/validation to use it, exports matchFingerprint, and logs Tier 1 vs fingerprint disagreements.

Changes

Cohort / File(s) Summary
Data and configuration
data/app-fingerprints.json, data/dev-pubkeys.json
Rewrote fingerprint entries to replace content-pattern rules with tag-based fingerprints (adds tag_pattern and tag_name_present rules for Bridge traffic, momostr, lnp2pbot, Ditto Trends, bitbot, and updated Primal/Snort/Damus/Amethyst rules). Added data/dev-pubkeys.json with informational developer public-key metadata.
Types
src/types.ts
Extended AppFingerprint interface with optional _comment?: string and tag_name_present?: string[].
Attribution logic
src/attribution/fingerprints.ts, src/attribution/resolve.ts
Added validation to require tag_name_present be string[]; extended matchFingerprint to check tag_name_present presence in event.tags, exported matchFingerprint, and added a Tier 1 clientTag vs fingerprint cross-check that emits a console.warn on disagreement.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble tags and chase the trace,
New fingerprints hop into place.
Bridges, bots, and momostr's song,
Matchers hum and logs sing along.
Hooray — tiny hops, a clearer race!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: deploying validated fingerprint rules derived from a client study to improve attribution coverage.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from #27: new tag_name_present field, fingerprint validation, tag_name_present matching logic, 14 validated rules, disagreement logging, and dev-pubkeys reference file.
Out of Scope Changes check ✅ Passed All changes directly support the objectives in #27: type extensions, validation logic, matching logic, fingerprint data, and developer reference are all within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch deploy-fingerprint-rules

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
data/app-fingerprints.json (1)

3-6: Consider moving the broad proxy rule below specific service rules.

Because Tier 4 picks the first match, this generic rule can preempt more specific attributions when both match. If specificity is preferred, place Bridge traffic later or add explicit rule priority.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data/app-fingerprints.json` around lines 3 - 6, The "Bridge traffic"
fingerprint (app_name "Bridge traffic", tag_name_present ["proxy"]) is a broad
rule that can preempt more specific Tier 4 matches; move this fingerprint entry
after more specific service fingerprints or add an explicit priority field
(e.g., "priority": lower_number_for_higher_priority) so specific rules are
evaluated first. Locate the "Bridge traffic" object (app_name "Bridge traffic")
and either relocate its entire JSON object to after service-specific rule
objects or add/adjust a priority key to ensure it has lower precedence than
specific services; update any Tier evaluation logic if you add a priority field
so Tier 4 still respects explicit priorities.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/attribution/fingerprints.ts`:
- Around line 28-30: The validation for tag_pattern in
src/attribution/fingerprints.ts is too loose and can allow null or non-object
entries that later cause matchFingerprint (resolve.ts) to access pattern.tag and
crash; update the tag_pattern check so it requires an array where every element
is a non-null object with a string "tag" property (and, if applicable, other
optional string fields like "pattern"), similar to the existing content_pattern
and tag_name_present checks—i.e., change the condition on e.tag_pattern to
validate Array.isArray(e.tag_pattern) && e.tag_pattern.every(v => v && typeof v
=== 'object' && typeof v.tag === 'string') to reject malformed entries.

In `@src/attribution/resolve.ts`:
- Around line 18-24: The current loop in resolve.ts flags disagreements by
comparing fp.app_name to clientTag.name, which yields false positives when
display names differ from canonical tag tokens (e.g., "Primal Web" vs
"primal-web"); update the check to compare the fingerprint app_name against the
client tag's canonical identifier(s) (for example clientTag.token or
clientTag.patterns) or a normalized/canonicalized form of clientTag (lowercase,
spaces→dashes, remove punctuation), using matchFingerprint(event, fp) and then
checking fp.app_name against that canonical value(s) instead of clientTag.name
so only true mismatches are logged.

---

Nitpick comments:
In `@data/app-fingerprints.json`:
- Around line 3-6: The "Bridge traffic" fingerprint (app_name "Bridge traffic",
tag_name_present ["proxy"]) is a broad rule that can preempt more specific Tier
4 matches; move this fingerprint entry after more specific service fingerprints
or add an explicit priority field (e.g., "priority":
lower_number_for_higher_priority) so specific rules are evaluated first. Locate
the "Bridge traffic" object (app_name "Bridge traffic") and either relocate its
entire JSON object to after service-specific rule objects or add/adjust a
priority key to ensure it has lower precedence than specific services; update
any Tier evaluation logic if you add a priority field so Tier 4 still respects
explicit priorities.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83a61ef4-fc26-4c78-81c0-873d9e598103

📥 Commits

Reviewing files that changed from the base of the PR and between 57442d0 and 8e44038.

📒 Files selected for processing (5)
  • data/app-fingerprints.json
  • data/dev-pubkeys.json
  • src/attribution/fingerprints.ts
  • src/attribution/resolve.ts
  • src/types.ts

Comment on lines 28 to +30
if (e.tag_pattern !== undefined && !Array.isArray(e.tag_pattern)) return false;
if (e.content_pattern !== undefined && !(Array.isArray(e.content_pattern) && e.content_pattern.every(v => typeof v === 'string'))) return false;
if (e.tag_name_present !== undefined && !(Array.isArray(e.tag_name_present) && e.tag_name_present.every(v => typeof v === 'string'))) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden tag_pattern element validation to prevent matcher crashes.

Line 30 correctly validates tag_name_present, but tag_pattern still accepts malformed elements. A null item can later crash matchFingerprint when Line 70 in src/attribution/resolve.ts reads pattern.tag.

Proposed fix
-      if (e.tag_pattern !== undefined && !Array.isArray(e.tag_pattern)) return false;
+      if (
+        e.tag_pattern !== undefined &&
+        !(
+          Array.isArray(e.tag_pattern) &&
+          e.tag_pattern.every((p) => {
+            if (typeof p !== 'object' || p === null) return false;
+            const pattern = p as Record<string, unknown>;
+            if (typeof pattern.tag !== 'string') return false;
+            if (pattern.value !== undefined && typeof pattern.value !== 'string') return false;
+            return true;
+          })
+        )
+      ) return false;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (e.tag_pattern !== undefined && !Array.isArray(e.tag_pattern)) return false;
if (e.content_pattern !== undefined && !(Array.isArray(e.content_pattern) && e.content_pattern.every(v => typeof v === 'string'))) return false;
if (e.tag_name_present !== undefined && !(Array.isArray(e.tag_name_present) && e.tag_name_present.every(v => typeof v === 'string'))) return false;
if (
e.tag_pattern !== undefined &&
!(
Array.isArray(e.tag_pattern) &&
e.tag_pattern.every((p) => {
if (typeof p !== 'object' || p === null) return false;
const pattern = p as Record<string, unknown>;
if (typeof pattern.tag !== 'string') return false;
if (pattern.value !== undefined && typeof pattern.value !== 'string') return false;
return true;
})
)
) return false;
if (e.content_pattern !== undefined && !(Array.isArray(e.content_pattern) && e.content_pattern.every(v => typeof v === 'string'))) return false;
if (e.tag_name_present !== undefined && !(Array.isArray(e.tag_name_present) && e.tag_name_present.every(v => typeof v === 'string'))) return false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/attribution/fingerprints.ts` around lines 28 - 30, The validation for
tag_pattern in src/attribution/fingerprints.ts is too loose and can allow null
or non-object entries that later cause matchFingerprint (resolve.ts) to access
pattern.tag and crash; update the tag_pattern check so it requires an array
where every element is a non-null object with a string "tag" property (and, if
applicable, other optional string fields like "pattern"), similar to the
existing content_pattern and tag_name_present checks—i.e., change the condition
on e.tag_pattern to validate Array.isArray(e.tag_pattern) &&
e.tag_pattern.every(v => v && typeof v === 'object' && typeof v.tag ===
'string') to reject malformed entries.

Comment on lines +18 to +24
// Check if any fingerprint disagrees with the client tag
for (const fp of fingerprints) {
if (matchFingerprint(event, fp) && fp.app_name.toLowerCase() !== clientTag.name.toLowerCase()) {
console.warn(`[fingerprint-disagree] client_tag="${clientTag.name}" fingerprint="${fp.app_name}" event=${event.id.slice(0, 8)}`);
break;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Disagreement check can produce false positives due to canonical-name mismatch.

Comparing fp.app_name to clientTag.name (Line 20) flags valid matches when display name and client tag token differ (e.g., Primal Web vs primal-web). This will pollute disagreement logs.

Proposed fix (compare against client-tag patterns, not display name)
   if (clientTag) {
-    // Check if any fingerprint disagrees with the client tag
+    // Check disagreement only among fingerprints that define a client tag value
+    const normalizedClient = clientTag.name.toLowerCase();
     for (const fp of fingerprints) {
-      if (matchFingerprint(event, fp) && fp.app_name.toLowerCase() !== clientTag.name.toLowerCase()) {
+      const clientValues = fp.tag_pattern
+        ?.filter(p => p.tag === 'client' && typeof p.value === 'string')
+        .map(p => p.value!.toLowerCase()) ?? [];
+      if (clientValues.length === 0) continue;
+      if (!matchFingerprint(event, fp)) continue;
+      if (!clientValues.includes(normalizedClient)) {
         console.warn(`[fingerprint-disagree] client_tag="${clientTag.name}" fingerprint="${fp.app_name}" event=${event.id.slice(0, 8)}`);
         break;
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/attribution/resolve.ts` around lines 18 - 24, The current loop in
resolve.ts flags disagreements by comparing fp.app_name to clientTag.name, which
yields false positives when display names differ from canonical tag tokens
(e.g., "Primal Web" vs "primal-web"); update the check to compare the
fingerprint app_name against the client tag's canonical identifier(s) (for
example clientTag.token or clientTag.patterns) or a normalized/canonicalized
form of clientTag (lowercase, spaces→dashes, remove punctuation), using
matchFingerprint(event, fp) and then checking fp.app_name against that canonical
value(s) instead of clientTag.name so only true mismatches are logged.

Rules matching on tag_pattern [{tag:"client", value:X}] can never fire
at Tier 4 — extractClientTag catches the same events at Tier 1 and
returns before fingerprints are consulted. Keeping them also caused
false disagreement warnings for alias pairs like primal-web/Primal Web.

Retain the 5 non-client-tag rules (Bridge traffic, momostr, lnp2pbot,
Ditto Trends, bitbot) which are the only rules that actually reach
Tier 4 and produce new attributions.

Client tag values documented in the fingerprint study report and
dev-pubkeys.json remain as reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deploy fingerprint attribution rules from study

1 participant