-
Notifications
You must be signed in to change notification settings - Fork 2
Add tlsn identity assign and remove logics #664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: testnet
Are you sure you want to change the base?
Conversation
Review Summary by QodoAdd TLSNotary identity verification and management
WalkthroughsDescription• Add TLSNotary identity verification for GitHub, Discord, Telegram • Implement identity add/remove operations via TLSN proof verification • Create server-side proof structure validation module with WASM support • Integrate TLSN identity handling into GCR identity routines and request handlers Diagramflowchart LR
A["TLSNotary Proof"] -->|verifyTLSNotaryPresentation| B["Proof Validation"]
B -->|parseHttpResponse| C["Extract User Data"]
C -->|extractUser| D["Verified Identity"]
D -->|applyTLSNIdentityAdd| E["GCR Storage"]
E -->|IncentiveManager| F["Award Incentives"]
G["Remove Request"] -->|applyTLSNIdentityRemove| H["GCR Update"]
H -->|IncentiveManager| I["Rollback Incentives"]
File Changes1. src/libs/tlsnotary/verifier.ts
|
WalkthroughIntroduces TLSNotary integration for Web2 identity verification. Adds a new TLSNotary verification module with proof validation, HTTP response parsing, and user extraction. Integrates TLSNotary identity add/remove operations into GCR identity routines, extends identity request handlers to support TLSNotary flows, and expands supported identity contexts. Changes
Sequence DiagramsequenceDiagram
actor Client
participant Handler as Identity Request<br/>Handler
participant Verifier as TLSNotary<br/>Verifier
participant GCRRoutines as GCR Identity<br/>Routines
participant Parser as HTTP Response<br/>Parser
participant DB as GCR<br/>Database
participant Incentive as Incentive<br/>Manager
Client->>Handler: POST tlsn_identity_assign
Handler->>Verifier: verifyTLSNProof(payload)
Verifier->>Verifier: validate context<br/>(github/discord/telegram)
Verifier->>Verifier: structure validation<br/>of proof
Verifier->>Parser: parseHttpResponse()
Parser->>Parser: extract statusLine,<br/>headers, body
Verifier->>Verifier: extractUser(context,<br/>responseBody)
Verifier-->>Handler: {success, extractedUsername}
Handler->>GCRRoutines: applyTLSNIdentityAdd(editOp)
GCRRoutines->>GCRRoutines: structural/semantic<br/>validation
GCRRoutines->>GCRRoutines: WASM crypto<br/>verification
GCRRoutines->>GCRRoutines: server-name &<br/>cross-field matching
GCRRoutines->>DB: persist identity
GCRRoutines->>Incentive: trigger context<br/>incentives
Incentive-->>GCRRoutines: success
GCRRoutines-->>Handler: GCRResult
Handler-->>Client: success response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
Code Review by Qodo
1. PII in TLSN logs
|
| log.info( | ||
| `[TLSN Identity] Verifying proof for ${context} identity: ${username}`, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. Pii in tlsn logs 📘 Rule violation ⛨ Security
The new TLSN identity flow logs username and userId, which can be considered personal user information and must not appear in application logs. These logs are also plain interpolated strings, making them harder to audit/parse consistently.
Agent Prompt
## Issue description
TLSN identity-related log messages include personal user information (`username`, `userId`) and are emitted as unstructured interpolated strings.
## Issue Context
Compliance requires logs to avoid sensitive/personal user data and be useful for auditing. Identity operations are security-sensitive, so logging must be carefully redacted and ideally structured.
## Fix Focus Areas
- src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts[1231-1331]
- src/libs/tlsnotary/verifier.ts[326-328]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (!verified.success) { | ||
| log.warn( | ||
| `[TLSN Identity] Proof verification failed: ${verified.error}`, | ||
| ) | ||
| return { | ||
| success: false, | ||
| message: `Proof verification failed: ${verified.error}`, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. Internal errors returned to clients 📘 Rule violation ⛨ Security
Several TLSN verification failures return verified.error and other internal details (e.g., expected/actual server names) in the message, which can leak implementation details to end-users. Detailed failure context should be kept in internal logs, while client-facing messages remain generic.
Agent Prompt
## Issue description
TLSN-related APIs return internal verification details (`verified.error`, server mismatch specifics) in response messages, risking information leakage.
## Issue Context
Client-facing errors should be generic per secure error-handling requirements; detailed context should be logged internally for debugging.
## Fix Focus Areas
- src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts[1238-1260]
- src/libs/tlsnotary/verifier.ts[317-324]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| // WASM verification disabled - trust claimed data with warning | ||
| // NOTE: This is less secure but allows operation until WASM works in Node.js | ||
| log.warn( | ||
| `[TLSN Identity] WASM disabled - trusting claimed data for ${context}: ${username} (${userId})`, | ||
| ) | ||
| extractedUser = { username, userId: String(userId) } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3. Tlsn add trusts client claims 📘 Rule violation ⛨ Security
When operating in structure-validation-only mode (or when verified.recv is absent), the code proceeds by trusting client-provided username/userId rather than deriving them from a verified proof. This is insufficient validation for an externally supplied identity claim and can enable identity spoofing.
Agent Prompt
## Issue description
TLSN identity assignment can succeed without server-side cryptographic verification by trusting client claims when running in structure-only verification mode.
## Issue Context
Identity binding is security-sensitive. Validating only proof structure is insufficient to authenticate the claimed `username`/`userId`.
## Fix Focus Areas
- src/libs/tlsnotary/verifier.ts[1-167]
- src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts[1230-1332]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (!simulate) { | ||
| await gcrMainRepository.save(accountGCR) | ||
|
|
||
| // Trigger incentive rollback if applicable | ||
| if (context === "github" && identity.userId) { | ||
| await IncentiveManager.githubUnlinked( | ||
| editOperation.account, | ||
| identity.userId, | ||
| ) | ||
| } else if (context === "discord") { | ||
| await IncentiveManager.discordUnlinked(editOperation.account) | ||
| } else if (context === "telegram") { | ||
| await IncentiveManager.telegramUnlinked(editOperation.account) | ||
| } | ||
| } | ||
|
|
||
| return { success: true, message: "TLSN identity removed successfully" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4. Tlsn remove lacks audit log 📘 Rule violation ✧ Quality
The TLSN identity removal operation performs a write (gcrMainRepository.save) without emitting an audit log capturing who performed the action, what changed, and the outcome. This makes it harder to reconstruct identity changes during incident response or compliance reviews.
Agent Prompt
## Issue description
TLSN identity removal writes state without an accompanying audit log entry describing the action and outcome.
## Issue Context
Identity add/remove operations are critical security events and should be traceable without exposing sensitive user data.
## Fix Focus Areas
- src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts[1422-1473]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const { context, username } = editOperation.data | ||
|
|
||
| if (!context || !username) { | ||
| return { | ||
| success: false, | ||
| message: "Invalid payload: missing context or username", | ||
| } | ||
| } | ||
|
|
||
| const accountGCR = await ensureGCRForUser(editOperation.account) | ||
|
|
||
| accountGCR.identities.web2 = accountGCR.identities.web2 || {} | ||
| accountGCR.identities.web2[context] = | ||
| accountGCR.identities.web2[context] || [] | ||
|
|
||
| // Find the identity to remove | ||
| const identity = accountGCR.identities.web2[context].find( | ||
| (id: Web2GCRData["data"]) => id.username === username, | ||
| ) | ||
|
|
||
| if (!identity) { | ||
| return { success: false, message: "Identity not found" } | ||
| } | ||
|
|
||
| // Filter out the identity | ||
| accountGCR.identities.web2[context] = accountGCR.identities.web2[ | ||
| context | ||
| ].filter((id: Web2GCRData["data"]) => id.username !== username) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5. Tlsn remove deletes non-tlsn 🐞 Bug ✓ Correctness
applyTLSNIdentityRemove deletes identities purely by context+username and does not check that the record was added via TLSN (proofType). This allows a tlsn_remove operation to remove legacy web2 identities and trigger incentive rollback unexpectedly.
Agent Prompt
### Issue description
`applyTLSNIdentityRemove` removes any web2 identity matching `username` without checking `proofType === "tlsn"`, and without validating `context` is one of the TLSN-supported platforms. This can delete non-TLSN identities.
### Issue Context
TLSN identities are stored in the same `accountGCR.identities.web2[context]` arrays used for regular web2 identities.
### Fix Focus Areas
- src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts[1422-1470]
- src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts[197-415]
### Suggested changes
1. Validate `context` in remove (e.g., only `github|discord|telegram`).
2. When finding/filtering, require the record to be TLSN-added, e.g. `(id as any).proofType === "tlsn"`.
3. Prefer removing by `userId` (or both `userId` and `username`) to avoid removing the wrong record when usernames collide or change.
4. Only trigger TLSN incentive rollback when the removed record is confirmed TLSN provenance.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/libs/omniprotocol/protocol/handlers/gcr.ts (1)
46-49:⚠️ Potential issue | 🟡 MinorStale doc comment — doesn't mention
nomisortlsncontexts.Line 49 says "apply identity changes (xm, web2, pqc, ud)" but should now include
nomisandtlsn.- * Uses GCRIdentityRoutines to apply identity changes (xm, web2, pqc, ud). + * Uses GCRIdentityRoutines to apply identity changes (xm, web2, pqc, ud, nomis, tlsn).
🤖 Fix all issues with AI agents
In `@src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts`:
- Around line 1165-1173: The JSDoc for the "Add an identity via TLSNotary proof
verification" routine is inaccurate: the code path currently falls back to
trusting client-provided claims because verifyTLSNotaryPresentation returns no
recv data, so cryptographic verification is not performed. Update the comment on
the method in GCRIdentityRoutines.ts to state the current behavior: that
verifyTLSNotaryPresentation does not yield cryptographically-extracted recv data
and the implementation therefore accepts client-supplied claims in the fallback
branch (where the code uses the client-provided values), and remove or soften
any absolute statements like "never trusting client-provided claims" and
"performs cryptographic verification."
- Around line 1267-1332: The code block that depends on verified.recv is dead
because verifyTLSNotaryPresentation never returns recv; update the TLSN
verification flow in GCRIdentityRoutines.verify... to avoid implying proof
verification: add a top-of-function TODO comment stating "identity claims are
currently unverified — WASM recv path disabled" and either remove/disable the
entire recv-based branch (the if (verified.recv) block that calls
parseHttpResponse, extractUser, and performs username/userId checks) or gate it
explicitly behind a clearly named feature flag (e.g., ENABLE_WASM_RECV) so it
cannot run accidentally; also update the log messages (the else branch and any
success logs) to make it explicit they are trusting client claims, and keep
references to verifyTLSNotaryPresentation, verified.recv, parseHttpResponse,
extractUser, and TLSNIdentityContext to locate the code.
- Around line 1422-1449: The applyTLSNIdentityRemove function currently calls
ensureGCRForUser which can create a new GCR record; change it to load the record
with gcrMainRepository.findOneBy({ account: editOperation.account }) (same
pattern as applyXmIdentityRemove / applyNomisIdentityRemove), and if no record
is found return { success: false, message: "Account not found" } instead of
proceeding; keep the rest of the logic that accesses accountGCR.identities.web2
the same so you only avoid creating empty GCRs when removing an identity.
- Around line 1180-1212: The destructured fields proofString, username, and
userId (from editOperation.data.data) are not validated and can be undefined;
add explicit checks in the GCRIdentityRoutines method after parsing proof
(and/or immediately after destructuring) to ensure username and userId are
present and non-empty (and that proof was successfully parsed), returning a
failure object with a clear message (e.g., "Missing required field: username" or
"Missing required field: userId") if they are missing; keep existing context
validation (this.TLSN_EXPECTED_ENDPOINTS[context]) but perform these new
validations before any use of username/userId (e.g., before logging or storing
at later lines) to prevent downstream errors.
- Around line 1396-1410: The TLSN branch calling IncentiveManager.telegramLinked
is missing the attestation argument; update the call in GCRIdentityRoutines
(where isFirstConnection is used) to pass data.proof as the fourth parameter so
the call becomes IncentiveManager.telegramLinked(editOperation.account,
String(userId), referralCode, data.proof), matching the Web2 path and allowing
awardTelegramPoints to inspect attestation.payload.group_membership.
In `@src/libs/omniprotocol/protocol/handlers/gcr.ts`:
- Around line 100-107: Update the stale doc comment that enumerates allowed
context types to include "nomis" and "tlsn" and add a short TODO noting the
temporary nature of the `as any` cast; specifically, update the comment near the
use of gcrIdentityRoutines.apply to list "xm, web2, pqc, ud, nomis, tlsn" and
add a note that the editOperation is currently cast to any due to a missing
"tlsn" union member in the SDK's GCREdit type; keep the `as any` for now but add
a TODO referencing `@kynesyslabs/demosdk` and instruct to remove the cast and
restore proper typing of editOperation to GCREdit once the SDK is updated.
In `@src/libs/tlsnotary/index.ts`:
- Around line 1-6: Update the module doc comment to remove the incorrect "using
WASM" claim and clearly state that this TLSNotary Verification Module performs
server-side, structure-only verification without WASM support; reference the
verifier implementation (verifier.ts) and its structure-only verification mode
so readers know where the non-WASM logic lives and that WASM is intentionally
unsupported.
In `@src/libs/tlsnotary/verifier.ts`:
- Around line 264-277: The extractUser logic in extractUser (case "telegram")
currently returns an empty string when both user.username and user.first_name
are missing, which can silently match in applyTLSNIdentityAdd's username
comparison; update extractUser to treat a missing username as an extraction
failure by checking that either user.username or user.first_name exists before
returning, and if neither exists log a clear warning/error (include context like
"Telegram response missing username/first_name") and return null instead of
username:""; ensure callers like applyTLSNIdentityAdd rely on null to indicate
extraction failure.
- Around line 301-335: The function verifyTLSNProof currently returns
caller-supplied username/userId as extractedUsername/extractedUserId which is
misleading; update verifyTLSNProof to either (A) rename the return fields to
claimedUsername and claimedUserId and adjust callers (e.g.,
handleIdentityRequest.ts handling of tlsn_identity_assign) to reflect they are
unverified claims, or (B) implement proof-derived extraction by parsing the
verified proof inside verifyTLSNProof (similar to applyTLSNIdentityAdd) and
compare those extracted values with the supplied username/userId, returning the
extracted values only when they match; choose one approach and update all uses
of extractedUsername/extractedUserId accordingly to avoid implying proof-derived
trust.
🧹 Nitpick comments (4)
src/libs/tlsnotary/verifier.ts (2)
138-152: Consider adding an upper bound ondatalength before running the regex test.
/^[0-9a-fA-F]+$/.test(presentationJSON.data)will scan the entire string. Ifdatais extremely large (megabytes of hex), this could be expensive. A length ceiling before the regex would be a simple safeguard.+ // Upper bound check to prevent processing excessively large payloads + if (presentationJSON.data.length > 10_000_000) { + return { + success: false, + error: "Invalid presentation: 'data' field exceeds maximum allowed size", + } + } + // Validate data is hex-encoded (basic check) if (!/^[0-9a-fA-F]+$/.test(presentationJSON.data)) {
82-95:initTLSNotaryVerifierandisVerifierInitializedare no-ops — document that clearly or remove the initialization ceremony.These functions exist "for API compatibility" but serve no purpose. Any caller checking
isVerifierInitialized()will always gettrue, which masks the fact that no real verification infrastructure is running. If these are meant to be a future extension point, the doc comment onisVerifierInitializedshould warn that it does not indicate cryptographic readiness.src/libs/network/routines/transactions/handleIdentityRequest.ts (1)
102-104: Verify the shape ofpayload.payloadmatchesTLSNIdentityPayloadat runtime.The cast
payload.payload as TLSNIdentityPayloadassumes the payload containscontext,proof,username, anduserIdfields. If a malformed request omits any of these,verifyTLSNProofwill proceed withundefinedvalues — the context validation on line 310 ofverifier.tswould catch an invalid context, butundefinedproofwould still pass thetypeof proof !== "object"check (sincetypeof undefined === "undefined"), leading to a structure validation failure. This works incidentally but is fragile.Consider adding explicit field presence checks before the cast, consistent with how other cases validate their payloads.
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (1)
1350-1359: Storing the full TLSNotary proof object in the JSONB column could significantly bloat storage.TLSNotary proofs can contain large hex-encoded data fields. Storing the entire proof per identity in the
web2JSONB column could degrade query performance over time. Consider storing only theproofHashand persisting the full proof in a separate table or blob store if re-verification is needed.
| /** | ||
| * Add an identity via TLSNotary proof verification. | ||
| * | ||
| * This method performs cryptographic verification of the TLSNotary proof, | ||
| * extracts the proven data, and compares it with the claimed values. | ||
| * Only stores the identity if the proof is valid and claims match. | ||
| * | ||
| * Security: Data is extracted directly from the cryptographic proof, | ||
| * never trusting client-provided claims without verification. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doc comment claims cryptographic verification and "never trusting client-provided claims" — both are currently false.
The JSDoc states the method "performs cryptographic verification" and "Data is extracted directly from the cryptographic proof, never trusting client-provided claims without verification." In reality, since verifyTLSNotaryPresentation returns no recv data, the code always falls to the else branch (line 1325) which explicitly trusts client claims. This doc will mislead any reviewer or auditor.
Update the doc to accurately reflect the current trust model.
🤖 Prompt for AI Agents
In `@src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts` around lines
1165 - 1173, The JSDoc for the "Add an identity via TLSNotary proof
verification" routine is inaccurate: the code path currently falls back to
trusting client-provided claims because verifyTLSNotaryPresentation returns no
recv data, so cryptographic verification is not performed. Update the comment on
the method in GCRIdentityRoutines.ts to state the current behavior: that
verifyTLSNotaryPresentation does not yield cryptographically-extracted recv data
and the implementation therefore accepts client-supplied claims in the fallback
branch (where the code uses the client-provided values), and remove or soften
any absolute statements like "never trusting client-provided claims" and
"performs cryptographic verification."
| // Extract context from editOperation.data (top level) | ||
| const { context } = editOperation.data | ||
| // Extract nested data fields (proof, username, userId are inside data.data) | ||
| const { | ||
| proof: proofString, | ||
| username, | ||
| userId, | ||
| } = editOperation.data.data || {} | ||
| // referralCode is at the editOperation level | ||
| const referralCode = editOperation.referralCode | ||
|
|
||
| // Parse the proof JSON string back to object | ||
| let proof: any | ||
| try { | ||
| proof = | ||
| typeof proofString === "string" | ||
| ? JSON.parse(proofString) | ||
| : proofString | ||
| } catch (e) { | ||
| return { | ||
| success: false, | ||
| message: "Invalid proof: failed to parse proof JSON string", | ||
| } | ||
| } | ||
|
|
||
| // 1. Validate context is supported | ||
| const expected = this.TLSN_EXPECTED_ENDPOINTS[context] | ||
| if (!expected) { | ||
| return { | ||
| success: false, | ||
| message: `Unsupported TLSN context: ${context}`, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation on destructured fields from editOperation.data.data.
Line 1187 uses editOperation.data.data || {}, so if data.data is undefined, all destructured fields (proofString, username, userId) become undefined. The method continues to line 1206 where context (from editOperation.data) is checked, but username and userId are never validated before being used (e.g., logged at line 1232, stored at line 1353).
Add early validation for required fields:
Proposed fix
const {
proof: proofString,
username,
userId,
} = editOperation.data.data || {}
// referralCode is at the editOperation level
const referralCode = editOperation.referralCode
+ if (!username || userId === undefined || userId === null) {
+ return {
+ success: false,
+ message: "Invalid payload: missing username or userId",
+ }
+ }
+
// Parse the proof JSON string back to object📝 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.
| // Extract context from editOperation.data (top level) | |
| const { context } = editOperation.data | |
| // Extract nested data fields (proof, username, userId are inside data.data) | |
| const { | |
| proof: proofString, | |
| username, | |
| userId, | |
| } = editOperation.data.data || {} | |
| // referralCode is at the editOperation level | |
| const referralCode = editOperation.referralCode | |
| // Parse the proof JSON string back to object | |
| let proof: any | |
| try { | |
| proof = | |
| typeof proofString === "string" | |
| ? JSON.parse(proofString) | |
| : proofString | |
| } catch (e) { | |
| return { | |
| success: false, | |
| message: "Invalid proof: failed to parse proof JSON string", | |
| } | |
| } | |
| // 1. Validate context is supported | |
| const expected = this.TLSN_EXPECTED_ENDPOINTS[context] | |
| if (!expected) { | |
| return { | |
| success: false, | |
| message: `Unsupported TLSN context: ${context}`, | |
| } | |
| } | |
| // Extract context from editOperation.data (top level) | |
| const { context } = editOperation.data | |
| // Extract nested data fields (proof, username, userId are inside data.data) | |
| const { | |
| proof: proofString, | |
| username, | |
| userId, | |
| } = editOperation.data.data || {} | |
| // referralCode is at the editOperation level | |
| const referralCode = editOperation.referralCode | |
| if (!username || userId === undefined || userId === null) { | |
| return { | |
| success: false, | |
| message: "Invalid payload: missing username or userId", | |
| } | |
| } | |
| // Parse the proof JSON string back to object | |
| let proof: any | |
| try { | |
| proof = | |
| typeof proofString === "string" | |
| ? JSON.parse(proofString) | |
| : proofString | |
| } catch (e) { | |
| return { | |
| success: false, | |
| message: "Invalid proof: failed to parse proof JSON string", | |
| } | |
| } | |
| // 1. Validate context is supported | |
| const expected = this.TLSN_EXPECTED_ENDPOINTS[context] | |
| if (!expected) { | |
| return { | |
| success: false, | |
| message: `Unsupported TLSN context: ${context}`, | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts` around lines
1180 - 1212, The destructured fields proofString, username, and userId (from
editOperation.data.data) are not validated and can be undefined; add explicit
checks in the GCRIdentityRoutines method after parsing proof (and/or immediately
after destructuring) to ensure username and userId are present and non-empty
(and that proof was successfully parsed), returning a failure object with a
clear message (e.g., "Missing required field: username" or "Missing required
field: userId") if they are missing; keep existing context validation
(this.TLSN_EXPECTED_ENDPOINTS[context]) but perform these new validations before
any use of username/userId (e.g., before logging or storing at later lines) to
prevent downstream errors.
| // 5. Parse HTTP response and extract user data (if WASM provided recv data) | ||
| let extractedUser: { username: string; userId: string } | null = null | ||
|
|
||
| // 5. Parse HTTP response and extract user data | ||
| // if (!verified.recv) { | ||
| // return { | ||
| // success: false, | ||
| // message: "No response data in proof", | ||
| // } | ||
| // } | ||
|
|
||
| if (verified.recv) { | ||
| const httpResponse = parseHttpResponse(verified.recv) | ||
| if (!httpResponse) { | ||
| return { | ||
| success: false, | ||
| message: "Failed to parse HTTP response from proof", | ||
| } | ||
| } | ||
|
|
||
| // 6. Extract user data based on context | ||
| extractedUser = extractUser( | ||
| context as TLSNIdentityContext, | ||
| httpResponse.body, | ||
| ) | ||
|
|
||
| if (!extractedUser) { | ||
| return { | ||
| success: false, | ||
| message: `Failed to extract user data from ${context} response`, | ||
| } | ||
| } | ||
|
|
||
| // 7. CRITICAL SECURITY CHECK: Compare claimed vs extracted values | ||
| if (extractedUser.username !== username) { | ||
| log.warn( | ||
| `[TLSN Identity] Username mismatch: claimed "${username}", proof contains "${extractedUser.username}"`, | ||
| ) | ||
| return { | ||
| success: false, | ||
| message: `Username mismatch: claimed "${username}", proof contains "${extractedUser.username}"`, | ||
| } | ||
| } | ||
|
|
||
| if (extractedUser.userId !== String(userId)) { | ||
| log.warn( | ||
| `[TLSN Identity] UserId mismatch: claimed "${userId}", proof contains "${extractedUser.userId}"`, | ||
| ) | ||
| return { | ||
| success: false, | ||
| message: `UserId mismatch: claimed "${userId}", proof contains "${extractedUser.userId}"`, | ||
| } | ||
| } | ||
|
|
||
| log.info( | ||
| // `[TLSN Identity] Proof verified successfully for ${context}: ${username} (${userId})`, | ||
| `[TLSN Identity] Proof verified with WASM for ${context}: ${username} (${userId})`, | ||
| ) | ||
| } else { | ||
| // WASM verification disabled - trust claimed data with warning | ||
| // NOTE: This is less secure but allows operation until WASM works in Node.js | ||
| log.warn( | ||
| `[TLSN Identity] WASM disabled - trusting claimed data for ${context}: ${username} (${userId})`, | ||
| ) | ||
| extractedUser = { username, userId: String(userId) } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recv-based extraction path is dead code; the else branch always executes.
verifyTLSNotaryPresentation never returns recv in its result (see verifier.ts line 156-160 — only success, time, verifyingKey are returned). So the if (verified.recv) block (lines 1278-1324) — including the critical security checks — never executes. The else branch (lines 1325-1332) always runs, unconditionally trusting client claims.
The commented-out block (lines 1271-1276) reinforces that this was recognized but deferred. At minimum, add a clear // TODO at the top of the method noting that all identity claims are currently unverified, and remove or gate the dead code to avoid giving a false impression of security.
🤖 Prompt for AI Agents
In `@src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts` around lines
1267 - 1332, The code block that depends on verified.recv is dead because
verifyTLSNotaryPresentation never returns recv; update the TLSN verification
flow in GCRIdentityRoutines.verify... to avoid implying proof verification: add
a top-of-function TODO comment stating "identity claims are currently unverified
— WASM recv path disabled" and either remove/disable the entire recv-based
branch (the if (verified.recv) block that calls parseHttpResponse, extractUser,
and performs username/userId checks) or gate it explicitly behind a clearly
named feature flag (e.g., ENABLE_WASM_RECV) so it cannot run accidentally; also
update the log messages (the else branch and any success logs) to make it
explicit they are trusting client claims, and keep references to
verifyTLSNotaryPresentation, verified.recv, parseHttpResponse, extractUser, and
TLSNIdentityContext to locate the code.
| } else if (context === "telegram") { | ||
| const isFirst = await this.isFirstConnection( | ||
| "telegram", | ||
| { userId: String(userId) }, | ||
| gcrMainRepository, | ||
| editOperation.account, | ||
| ) | ||
|
|
||
| if (isFirst) { | ||
| await IncentiveManager.telegramLinked( | ||
| editOperation.account, | ||
| String(userId), | ||
| referralCode, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find the telegramLinked function definition to understand its signature
rg -n 'telegramLinked\s*\(' --type=ts -A5 | head -100Repository: kynesyslabs/node
Length of output: 1948
🏁 Script executed:
# Look at the specific lines mentioned in the review
sed -n '322,327p' src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts
sed -n '1396,1410p' src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.tsRepository: kynesyslabs/node
Length of output: 916
🏁 Script executed:
# Find the file containing IncentiveManager to locate telegramLinked
fd -t f -e ts -e js | xargs rg -l 'telegramLinked' | grep -i incentiveRepository: kynesyslabs/node
Length of output: 116
🏁 Script executed:
# Get the full implementation of telegramLinked method
sed -n '93,150p' src/libs/blockchain/gcr/gcr_routines/IncentiveManager.tsRepository: kynesyslabs/node
Length of output: 1699
🏁 Script executed:
# Also search for references to attestation or group_membership in IncentiveManager
rg -n 'attestation|group_membership' src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts -A2Repository: kynesyslabs/node
Length of output: 278
🏁 Script executed:
# Find awardTelegramPoints method and check if it uses attestation
rg -n 'awardTelegramPoints' --type=ts -A20Repository: kynesyslabs/node
Length of output: 3610
🏁 Script executed:
# Also check if group_membership is referenced anywhere
rg -n 'group_membership' --type=ts -B2 -A2Repository: kynesyslabs/node
Length of output: 1463
🏁 Script executed:
# Check what happens when isGroupMember is false
sed -n '880,920p' src/features/incentive/PointSystem.tsRepository: kynesyslabs/node
Length of output: 1604
🏁 Script executed:
# Check what variables are available in the TLSN path context around line 1396-1410
sed -n '1370,1420p' src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.tsRepository: kynesyslabs/node
Length of output: 1755
🏁 Script executed:
# Also check if there's a proof/attestation variable available in the TLSN context
sed -n '1360,1430p' src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts | head -100Repository: kynesyslabs/node
Length of output: 2361
Pass data.proof (TelegramSignedAttestation) as the fourth argument to IncentiveManager.telegramLinked.
The TLSN path omits the attestation parameter that the Web2 path (line 326) passes as data.proof. The awardTelegramPoints method checks attestation?.payload?.group_membership === true to determine eligibility for incentive points. Without it, the check evaluates to false and users receive 0 points with the message "you must join the required group to earn points"—even though points should be awarded on successful linking for group members. Pass data.proof as the fourth argument to match the Web2 path behavior.
🤖 Prompt for AI Agents
In `@src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts` around lines
1396 - 1410, The TLSN branch calling IncentiveManager.telegramLinked is missing
the attestation argument; update the call in GCRIdentityRoutines (where
isFirstConnection is used) to pass data.proof as the fourth parameter so the
call becomes IncentiveManager.telegramLinked(editOperation.account,
String(userId), referralCode, data.proof), matching the Web2 path and allowing
awardTelegramPoints to inspect attestation.payload.group_membership.
| static async applyTLSNIdentityRemove( | ||
| editOperation: any, | ||
| gcrMainRepository: Repository<GCRMain>, | ||
| simulate: boolean, | ||
| ): Promise<GCRResult> { | ||
| const { context, username } = editOperation.data | ||
|
|
||
| if (!context || !username) { | ||
| return { | ||
| success: false, | ||
| message: "Invalid payload: missing context or username", | ||
| } | ||
| } | ||
|
|
||
| const accountGCR = await ensureGCRForUser(editOperation.account) | ||
|
|
||
| accountGCR.identities.web2 = accountGCR.identities.web2 || {} | ||
| accountGCR.identities.web2[context] = | ||
| accountGCR.identities.web2[context] || [] | ||
|
|
||
| // Find the identity to remove | ||
| const identity = accountGCR.identities.web2[context].find( | ||
| (id: Web2GCRData["data"]) => id.username === username, | ||
| ) | ||
|
|
||
| if (!identity) { | ||
| return { success: false, message: "Identity not found" } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
applyTLSNIdentityRemove uses ensureGCRForUser instead of findOneBy — may create empty GCR records on remove.
Other remove methods (e.g., applyXmIdentityRemove at line 142, applyNomisIdentityRemove at line 1100) use gcrMainRepository.findOneBy(...) and return "Account not found" when the account doesn't exist. This method uses ensureGCRForUser, which creates a new GCR record if one doesn't exist, then proceeds to find no matching identity. This is wasteful and inconsistent.
Proposed fix
static async applyTLSNIdentityRemove(
editOperation: any,
gcrMainRepository: Repository<GCRMain>,
simulate: boolean,
): Promise<GCRResult> {
const { context, username } = editOperation.data
if (!context || !username) {
return {
success: false,
message: "Invalid payload: missing context or username",
}
}
- const accountGCR = await ensureGCRForUser(editOperation.account)
+ const accountGCR = await gcrMainRepository.findOneBy({
+ pubkey: editOperation.account,
+ })
+
+ if (!accountGCR) {
+ return { success: false, message: "Account not found" }
+ }
accountGCR.identities.web2 = accountGCR.identities.web2 || {}📝 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.
| static async applyTLSNIdentityRemove( | |
| editOperation: any, | |
| gcrMainRepository: Repository<GCRMain>, | |
| simulate: boolean, | |
| ): Promise<GCRResult> { | |
| const { context, username } = editOperation.data | |
| if (!context || !username) { | |
| return { | |
| success: false, | |
| message: "Invalid payload: missing context or username", | |
| } | |
| } | |
| const accountGCR = await ensureGCRForUser(editOperation.account) | |
| accountGCR.identities.web2 = accountGCR.identities.web2 || {} | |
| accountGCR.identities.web2[context] = | |
| accountGCR.identities.web2[context] || [] | |
| // Find the identity to remove | |
| const identity = accountGCR.identities.web2[context].find( | |
| (id: Web2GCRData["data"]) => id.username === username, | |
| ) | |
| if (!identity) { | |
| return { success: false, message: "Identity not found" } | |
| } | |
| static async applyTLSNIdentityRemove( | |
| editOperation: any, | |
| gcrMainRepository: Repository<GCRMain>, | |
| simulate: boolean, | |
| ): Promise<GCRResult> { | |
| const { context, username } = editOperation.data | |
| if (!context || !username) { | |
| return { | |
| success: false, | |
| message: "Invalid payload: missing context or username", | |
| } | |
| } | |
| const accountGCR = await gcrMainRepository.findOneBy({ | |
| pubkey: editOperation.account, | |
| }) | |
| if (!accountGCR) { | |
| return { success: false, message: "Account not found" } | |
| } | |
| accountGCR.identities.web2 = accountGCR.identities.web2 || {} | |
| accountGCR.identities.web2[context] = | |
| accountGCR.identities.web2[context] || [] | |
| // Find the identity to remove | |
| const identity = accountGCR.identities.web2[context].find( | |
| (id: Web2GCRData["data"]) => id.username === username, | |
| ) | |
| if (!identity) { | |
| return { success: false, message: "Identity not found" } | |
| } |
🤖 Prompt for AI Agents
In `@src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts` around lines
1422 - 1449, The applyTLSNIdentityRemove function currently calls
ensureGCRForUser which can create a new GCR record; change it to load the record
with gcrMainRepository.findOneBy({ account: editOperation.account }) (same
pattern as applyXmIdentityRemove / applyNomisIdentityRemove), and if no record
is found return { success: false, message: "Account not found" } instead of
proceeding; keep the rest of the logic that accesses accountGCR.identities.web2
the same so you only avoid creating empty GCRs when removing an identity.
| // Apply the identity operation (simulate = false for actual execution) | ||
| // Type assertion needed: local IdentityAssignRequest includes "tlsn" context | ||
| // but the GCREdit type from SDK package may not have it yet | ||
| const result = await gcrIdentityRoutines.apply( | ||
| editOperation, | ||
| editOperation as any, | ||
| gcrMainRepository, | ||
| false, // simulate = false (actually apply changes) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, locate the file and check its size
wc -l src/libs/omniprotocol/protocol/handlers/gcr.tsRepository: kynesyslabs/node
Length of output: 110
🏁 Script executed:
# Read the relevant sections of the file
sed -n '40,60p' src/libs/omniprotocol/protocol/handlers/gcr.tsRepository: kynesyslabs/node
Length of output: 834
🏁 Script executed:
# Read the section with the type assertion
sed -n '95,115p' src/libs/omniprotocol/protocol/handlers/gcr.tsRepository: kynesyslabs/node
Length of output: 993
🏁 Script executed:
# Check package.json for current SDK version
cat package.json | grep -A 2 -B 2 "demosdk"Repository: kynesyslabs/node
Length of output: 656
🌐 Web query:
@kynesyslabs/demosdk npm package latest version GCREdit type
💡 Result:
- Latest npm version of
@kynesyslabs/demosdk:2.3.22(shown as the current version on npm). [1] GCREdittype: I could not find any published API-reference entry or docs page mentioning aGCREdittype for@kynesyslabs/demosdk. [2]
To confirm whether GCREdit exists in the installed typings (and whether it’s exported), search your local install:
npm i `@kynesyslabs/demosdk`@latest
grep -R "GCREdit" node_modules/@kynesyslabs/demosdk -nSources: [1] [2]
🏁 Script executed:
# Search for GCREdit type in node_modules
find node_modules/@kynesyslabs/demosdk -name "*.d.ts" 2>/dev/null | head -20Repository: kynesyslabs/node
Length of output: 42
🏁 Script executed:
# Search for GCREdit in the SDK
grep -r "GCREdit" node_modules/@kynesyslabs/demosdk 2>/dev/null | head -10Repository: kynesyslabs/node
Length of output: 42
🏁 Script executed:
# Also check if we can find the actual IdentityAssignRequest type in the codebase
rg "IdentityAssignRequest" src/ -A 5 -B 2Repository: kynesyslabs/node
Length of output: 2041
🏁 Script executed:
# Check what contexts are validated in the code (around line 74-75 mentioned in scratchpad)
sed -n '70,80p' src/libs/omniprotocol/protocol/handlers/gcr.tsRepository: kynesyslabs/node
Length of output: 635
Update stale doc comment to include new context types, and track as any cast as tech debt.
The doc comment on line 49 lists only "xm, web2, pqc, ud" but should include the newly added "nomis" and "tlsn" contexts. The as any cast on line 104 is a pragmatic workaround for SDK type misalignment—once @kynesyslabs/demosdk is updated to include "tlsn" in GCREdit, remove this assertion and update the comment accordingly.
🤖 Prompt for AI Agents
In `@src/libs/omniprotocol/protocol/handlers/gcr.ts` around lines 100 - 107,
Update the stale doc comment that enumerates allowed context types to include
"nomis" and "tlsn" and add a short TODO noting the temporary nature of the `as
any` cast; specifically, update the comment near the use of
gcrIdentityRoutines.apply to list "xm, web2, pqc, ud, nomis, tlsn" and add a
note that the editOperation is currently cast to any due to a missing "tlsn"
union member in the SDK's GCREdit type; keep the `as any` for now but add a TODO
referencing `@kynesyslabs/demosdk` and instruct to remove the cast and restore
proper typing of editOperation to GCREdit once the SDK is updated.
| /** | ||
| * TLSNotary Verification Module | ||
| * | ||
| * Provides server-side verification of TLSNotary proofs using WASM. | ||
| * Used by GCR identity routines to verify TLSN-based identity claims. | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module doc comment is misleading — says "using WASM" but this module explicitly does not use WASM.
Line 4 states "Provides server-side verification of TLSNotary proofs using WASM" but the underlying verifier.ts operates in "structure-only verification mode" with WASM explicitly noted as unsupported. This will confuse future readers.
/**
* TLSNotary Verification Module
*
- * Provides server-side verification of TLSNotary proofs using WASM.
+ * Provides server-side structural validation of TLSNotary proofs.
+ * NOTE: Full cryptographic verification via WASM is not yet supported in Node.js.
* Used by GCR identity routines to verify TLSN-based identity claims.
*/📝 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.
| /** | |
| * TLSNotary Verification Module | |
| * | |
| * Provides server-side verification of TLSNotary proofs using WASM. | |
| * Used by GCR identity routines to verify TLSN-based identity claims. | |
| */ | |
| /** | |
| * TLSNotary Verification Module | |
| * | |
| * Provides server-side structural validation of TLSNotary proofs. | |
| * NOTE: Full cryptographic verification via WASM is not yet supported in Node.js. | |
| * Used by GCR identity routines to verify TLSN-based identity claims. | |
| */ |
🤖 Prompt for AI Agents
In `@src/libs/tlsnotary/index.ts` around lines 1 - 6, Update the module doc
comment to remove the incorrect "using WASM" claim and clearly state that this
TLSNotary Verification Module performs server-side, structure-only verification
without WASM support; reference the verifier implementation (verifier.ts) and
its structure-only verification mode so readers know where the non-WASM logic
lives and that WASM is intentionally unsupported.
| case "telegram": { | ||
| // Handle response format: { user: { id, username, first_name, ... } } | ||
| const user = json.user || json | ||
| if (user.id !== undefined) { | ||
| return { | ||
| username: user.username || user.first_name || "", | ||
| userId: String(user.id), | ||
| } | ||
| } | ||
| log.warn( | ||
| "[TLSNotary Verifier] Telegram response missing 'id' field", | ||
| ) | ||
| return null | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Telegram extractUser can return an empty username, which may cause downstream issues.
When neither user.username nor user.first_name is present, the returned username is "". This could silently pass validation in applyTLSNIdentityAdd (line 1301 compares extractedUser.username !== username) and cause identity lookups by username to match unrelated empty-username entries.
Consider treating a missing username as an extraction failure, or at minimum logging this edge case more prominently.
🤖 Prompt for AI Agents
In `@src/libs/tlsnotary/verifier.ts` around lines 264 - 277, The extractUser logic
in extractUser (case "telegram") currently returns an empty string when both
user.username and user.first_name are missing, which can silently match in
applyTLSNIdentityAdd's username comparison; update extractUser to treat a
missing username as an extraction failure by checking that either user.username
or user.first_name exists before returning, and if neither exists log a clear
warning/error (include context like "Telegram response missing
username/first_name") and return null instead of username:""; ensure callers
like applyTLSNIdentityAdd rely on null to indicate extraction failure.
| export async function verifyTLSNProof(payload: TLSNIdentityPayload): Promise<{ | ||
| success: boolean | ||
| message: string | ||
| extractedUsername?: string | ||
| extractedUserId?: string | ||
| }> { | ||
| const { context, proof, username, userId } = payload | ||
|
|
||
| // Validate context | ||
| if (!["github", "discord", "telegram"].includes(context)) { | ||
| return { | ||
| success: false, | ||
| message: `Unsupported TLSN context: ${context}`, | ||
| } | ||
| } | ||
|
|
||
| // Verify the proof structure | ||
| const verified = await verifyTLSNotaryPresentation(proof) | ||
| if (!verified.success) { | ||
| return { | ||
| success: false, | ||
| message: `Proof verification failed: ${verified.error}`, | ||
| } | ||
| } | ||
|
|
||
| log.info( | ||
| `[TLSNotary Verifier] ${context} proof structure validated for: username=${username}, userId=${userId}`, | ||
| ) | ||
|
|
||
| return { | ||
| success: true, | ||
| message: "Proof structure verified", | ||
| extractedUsername: username, | ||
| extractedUserId: userId, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security: verifyTLSNProof echoes back client-supplied claims as "extracted" values — misleading API contract.
The function names return fields extractedUsername and extractedUserId, implying they were derived from the proof. In reality, the function only validates proof structure and returns the caller-supplied username/userId verbatim. Any consumer of this API may incorrectly assume the values are proof-derived, creating a false sense of security.
This function is called in handleIdentityRequest.ts for tlsn_identity_assign, meaning the network-layer verification effectively rubber-stamps client claims.
Consider either:
- Renaming the fields to
claimedUsername/claimedUserIdto make the trust model explicit, or - Actually extracting data from the proof (when possible) and comparing it with claims here, similar to what
applyTLSNIdentityAddattempts.
Suggested field rename to clarify trust model
export async function verifyTLSNProof(payload: TLSNIdentityPayload): Promise<{
success: boolean
message: string
- extractedUsername?: string
- extractedUserId?: string
+ claimedUsername?: string
+ claimedUserId?: string
}> {
...
return {
success: true,
message: "Proof structure verified",
- extractedUsername: username,
- extractedUserId: userId,
+ claimedUsername: username,
+ claimedUserId: userId,
}
}🤖 Prompt for AI Agents
In `@src/libs/tlsnotary/verifier.ts` around lines 301 - 335, The function
verifyTLSNProof currently returns caller-supplied username/userId as
extractedUsername/extractedUserId which is misleading; update verifyTLSNProof to
either (A) rename the return fields to claimedUsername and claimedUserId and
adjust callers (e.g., handleIdentityRequest.ts handling of tlsn_identity_assign)
to reflect they are unverified claims, or (B) implement proof-derived extraction
by parsing the verified proof inside verifyTLSNProof (similar to
applyTLSNIdentityAdd) and compare those extracted values with the supplied
username/userId, returning the extracted values only when they match; choose one
approach and update all uses of extractedUsername/extractedUserId accordingly to
avoid implying proof-derived trust.



Summary by CodeRabbit