-
Notifications
You must be signed in to change notification settings - Fork 19
feat(node): iCaptcha proof-of-intelligence gate on create_repo + register #108
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: main
Are you sure you want to change the base?
Changes from all commits
d53b5e1
28a3dad
e976398
269d609
98121ca
66e7a34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -781,6 +781,21 @@ const MIGRATIONS: &[Migration] = &[ | |||||||||||||||||||||||||||||||||||||
| "CREATE INDEX IF NOT EXISTS idx_repos_owner_key_name ON repos ((CASE WHEN owner_did LIKE 'did:key:%' AND position(':' in substr(owner_did, 9)) = 0 THEN substr(owner_did, 9) ELSE owner_did END), name)", | ||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| Migration { | ||||||||||||||||||||||||||||||||||||||
| version: 8, | ||||||||||||||||||||||||||||||||||||||
| name: "icaptcha_consumed_proofs", | ||||||||||||||||||||||||||||||||||||||
| stmts: &[ | ||||||||||||||||||||||||||||||||||||||
| // Single-use ledger for iCaptcha proof ids (jti). A proof may be | ||||||||||||||||||||||||||||||||||||||
| // spent once per gated action; replays are rejected until the row | ||||||||||||||||||||||||||||||||||||||
| // is swept after the proof's own expiry. `expires_at` is the | ||||||||||||||||||||||||||||||||||||||
| // proof's unix-seconds exp, used for cleanup. | ||||||||||||||||||||||||||||||||||||||
| r#"CREATE TABLE IF NOT EXISTS icaptcha_consumed_proofs ( | ||||||||||||||||||||||||||||||||||||||
| jti TEXT NOT NULL PRIMARY KEY, | ||||||||||||||||||||||||||||||||||||||
| expires_at BIGINT NOT NULL | ||||||||||||||||||||||||||||||||||||||
| )"#, | ||||||||||||||||||||||||||||||||||||||
| "CREATE INDEX IF NOT EXISTS idx_icaptcha_consumed_expires ON icaptcha_consumed_proofs(expires_at)", | ||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // ── Repos ───────────────────────────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1097,6 +1112,45 @@ impl Db { | |||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /// Atomically consume an iCaptcha proof id (`jti`). Returns `Ok(true)` if it | ||||||||||||||||||||||||||||||||||||||
| /// was newly recorded (the proof may be used), `Ok(false)` if it was already | ||||||||||||||||||||||||||||||||||||||
| /// spent (a replay). `expires_at` is the proof's unix-seconds `exp`, kept so | ||||||||||||||||||||||||||||||||||||||
| /// the ledger row can be swept once the proof can no longer be valid. | ||||||||||||||||||||||||||||||||||||||
| pub async fn consume_proof_jti(&self, jti: &str, expires_at: i64) -> Result<bool> { | ||||||||||||||||||||||||||||||||||||||
| let result = sqlx::query( | ||||||||||||||||||||||||||||||||||||||
| "INSERT INTO icaptcha_consumed_proofs (jti, expires_at) | ||||||||||||||||||||||||||||||||||||||
| VALUES ($1, $2) | ||||||||||||||||||||||||||||||||||||||
| ON CONFLICT (jti) DO NOTHING", | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| .bind(jti) | ||||||||||||||||||||||||||||||||||||||
| .bind(expires_at) | ||||||||||||||||||||||||||||||||||||||
| .execute(&self.pool) | ||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||
| Ok(result.rows_affected() > 0) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /// Read-only check if an iCaptcha proof id (`jti`) has already been consumed. | ||||||||||||||||||||||||||||||||||||||
| /// Returns `Ok(true)` if the jti is fresh (not yet consumed), `Ok(false)` if | ||||||||||||||||||||||||||||||||||||||
| /// it is a replay. Used by shadow mode to detect replays without mutating state. | ||||||||||||||||||||||||||||||||||||||
| pub async fn check_proof_jti(&self, jti: &str) -> Result<bool> { | ||||||||||||||||||||||||||||||||||||||
| let exists = sqlx::query_scalar::<_, bool>( | ||||||||||||||||||||||||||||||||||||||
| "SELECT EXISTS(SELECT 1 FROM icaptcha_consumed_proofs WHERE jti = $1)", | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| .bind(jti) | ||||||||||||||||||||||||||||||||||||||
| .fetch_one(&self.pool) | ||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||
| Ok(!exists) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1132
to
+1143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Record first-seen proofs in shadow mode before checking replay.
Suggested direction- pub async fn check_proof_jti(&self, jti: &str) -> Result<bool> {
- let exists = sqlx::query_scalar::<_, bool>(
- "SELECT EXISTS(SELECT 1 FROM icaptcha_consumed_proofs WHERE jti = $1)",
- )
- .bind(jti)
- .fetch_one(&self.pool)
- .await?;
- Ok(!exists)
+ pub async fn observe_proof_jti(&self, jti: &str, expires_at: i64) -> Result<bool> {
+ self.consume_proof_jti(jti, expires_at).await
}Then update the shadow caller to log on - let is_fresh = db.check_proof_jti(&job.jti).await?;
+ let is_fresh = db.observe_proof_jti(&job.jti, job.exp).await?;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /// Delete consumed-proof rows whose proof has expired. Returns rows removed. | ||||||||||||||||||||||||||||||||||||||
| pub async fn sweep_expired_proofs(&self, now: i64) -> Result<u64> { | ||||||||||||||||||||||||||||||||||||||
| let result = sqlx::query("DELETE FROM icaptcha_consumed_proofs WHERE expires_at < $1") | ||||||||||||||||||||||||||||||||||||||
| .bind(now) | ||||||||||||||||||||||||||||||||||||||
| .execute(&self.pool) | ||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||
| Ok(result.rows_affected()) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| pub async fn get_trust_score(&self, agent_did: &str) -> Result<f64> { | ||||||||||||||||||||||||||||||||||||||
| let row = sqlx::query("SELECT trust_score FROM agents WHERE did = $1") | ||||||||||||||||||||||||||||||||||||||
| .bind(agent_did) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.