{"record":{"id":"545ac9e821087bbc","repo":"block/buzz","slug":"invalid-repo-identifier-must-be-a-za-z0-9-1","errorCode":null,"errorMessage":"invalid repo identifier: must be [a-zA-Z0-9._-]{1,64}, no leading dots, no '..'","messagePattern":"invalid repo identifier: must be \\[a-zA-Z0-9\\._-\\](.+?), no leading dots, no '\\.\\.'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":2571,"sourceCode":"pub(crate) struct GitRepoAnnouncementGate {\n    pub(crate) reached: tokio::sync::Notify,\n    pub(crate) resume: tokio::sync::Notify,\n}\n\npub(crate) async fn handle_git_repo_announcement_inner(\n    tenant: &TenantContext,\n    event: &Event,\n    state: &Arc<AppState>,\n    hooks: &GitRepoAnnouncementHooks,\n) -> anyhow::Result<()> {\n    #[cfg(not(test))]\n    let _ = hooks;\n    // Extract repo identifier from d tag (required for NIP-33 parameterized replaceable events).\n    let repo_id =\n        extract_tag_value(event, \"d\").ok_or_else(|| anyhow::anyhow!(\"kind:30617 missing d tag\"))?;\n\n    if !validate_repo_id(&repo_id) {\n        return Err(anyhow::anyhow!(\n            \"invalid repo identifier: must be [a-zA-Z0-9._-]{{1,64}}, no leading dots, no '..'\"\n        ));\n    }\n\n    let owner_hex = hex::encode(event.pubkey.to_bytes());\n\n    // The relay holds no persistent per-repo disk state: runtime reads and\n    // writes hydrate an ephemeral bare repo from object storage per request\n    // (see `api::git::hydrate`). Announce only (1) reserves the repo name and\n    // (2) seeds the empty-manifest pointer that makes the repo clone-able.\n    //\n    // The `git_repo_names` table (Postgres) is the relay's name registry,\n    // keyed `(community_id, repo_id)`. It serves three jobs at once inside the\n    // server-resolved community boundary:\n    //   - uniqueness: `INSERT … ON CONFLICT DO NOTHING` is atomic, so\n    //     concurrent kind:30617 events for the same community/name can't both\n    //     claim it (TOCTOU-free — the DB PK is the race guard);\n    //   - idempotent re-announce: a reservation owned by the same pubkey is an","sourceCodeStart":2553,"sourceCodeEnd":2589,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L2553-L2589","documentation":"After extracting the `d` tag from a kind:30617 announcement, the relay validates the repo identifier with validate_repo_id. This error means the identifier contains characters outside [a-zA-Z0-9._-], exceeds 64 characters, starts with a dot, or contains the '..' sequence — any of which could enable path traversal or ambiguous repo naming. The announcement is rejected.","triggerScenarios":"Publishing a kind:30617 announcement whose `d` tag value is e.g. \"my repo\", \"repo/name\", \".hidden\", \"a..b\", or a >64-char name.","commonSituations":"Repo names containing slashes or spaces copied from git remote URLs; users trying hierarchical repo ids like \"team/project\"; legacy repos with dots-leading names; automated scripts interpolating full URLs instead of bare repo names.","solutions":["Rename the repo identifier to contain only [a-zA-Z0-9._-], 1-64 chars.","Strip leading dots and collapse/remove any '..' sequence in the id before signing.","Re-publish the kind:30617 event with the corrected `d` tag value."],"exampleFix":"// before\n[[\"d\", \"team/../etc\"]]\n// after\n[[\"d\", \"team-etc\"]]  // or \"team.etc\"","handlingStrategy":"validation","validationCode":"fn valid_repo_id(id: &str) -> bool {\n    !id.is_empty() && id.len() <= 64 && !id.starts_with('.')\n        && !id.contains(\"..\")\n        && id.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.'|'_'|'-'))\n}","typeGuard":null,"tryCatchPattern":"let repo_id = extract_tag_value(&event, \"d\").context(\"missing d tag\")?;\nif !valid_repo_id(repo_id) {\n    return Err(anyhow!(\"rejecting announcement: invalid repo id {repo_id:?}\"));\n}","preventionTips":["Sanitize repo names (strip slashes/spaces) before placing them in the `d` tag.","Reject ids with leading dots or '..' at the client, mirroring validate_repo_id.","Cap repo id length at 64 chars in your tooling."],"tags":["nostr","validation","git","nip-33"],"backgroundTag":"invalid-identifier-format","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}