{"record":{"id":"f90aceba9098ab11","repo":"nextcloud/server","slug":"card-not-found-f90ace","errorCode":null,"errorMessage":"Card not found","messagePattern":"Card not found","errorType":"http","errorClass":"Sabre\\DAV\\Exception\\NotFound","httpStatus":404,"severity":"error","filePath":"apps/dav/lib/CardDAV/SystemAddressbook.php","lineNumber":208,"sourceCode":"\t\t\t\tforeach ($group->getUsers() as $groupUser) {\n\t\t\t\t\tif ($groupUser->getBackendClassName() === 'Guests') {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t$otherName = SyncService::getCardUri($groupUser);\n\t\t\t\t\tif ($otherName === $name) {\n\t\t\t\t\t\treturn parent::getChild($name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new Forbidden();\n\t\t}\n\t\tif (!$this->isFederation()) {\n\t\t\treturn parent::getChild($name);\n\t\t}\n\n\t\t$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);\n\t\tif (!$obj) {\n\t\t\tthrow new NotFound('Card not found');\n\t\t}\n\t\t$carddata = $this->extractCarddata($obj);\n\t\tif (empty($carddata)) {\n\t\t\tthrow new Forbidden();\n\t\t} else {\n\t\t\t$obj['carddata'] = $carddata;\n\t\t}\n\t\treturn new Card($this->carddavBackend, $this->addressBookInfo, $obj);\n\t}\n\t#[\\Override]\n\tpublic function getChanges($syncToken, $syncLevel, $limit = null) {\n\n\t\tif (!$this->carddavBackend instanceof SyncSupport) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!$this->isFederation()) {\n\t\t\treturn parent::getChanges($syncToken, $syncLevel, $limit);","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/CardDAV/SystemAddressbook.php#L190-L226","documentation":"For federated requests (basic-auth user 'system' with a trusted-server shared secret, per isFederation()), SystemAddressbook::getChild() bypasses the enumeration policy and fetches the card row directly via getCard(). If no row matches the requested URI it throws NotFound('Card not found') (HTTP 404). Reaching this branch already proves federation auth succeeded; the 404 means the card genuinely does not exist locally.","triggerScenarios":"A trusted remote server requesting a card URI that does not exist locally (user deleted, UID renamed, URI built with a different scheme); lookups issued after local user deletion while the remote cache is stale.","commonSituations":"Federated shares referencing removed users; remotes never refreshing their copy of the system addressbook; card URI generation differing between Nextcloud versions.","solutions":["On the remote side, treat the 404 as authoritative and prune the card from the federation cache","Verify the requested URI matches what SyncService::getCardUri(<user>) produces on the local server","If the user should exist, check they were not deleted and the card row exists in oc_cards for the system addressbook"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// remote-side: only GET cards that discovery still lists\n$status = $client->propFindStatus($systemBookUri . '/' . urlencode($name));\nif ($status === 404) {\n    $federationCache->prune($name);\n    return null;\n}","typeGuard":null,"tryCatchPattern":"use Sabre\\DAV\\Exception\\NotFound;\n\ntry {\n    $card = $systemAddressbook->getChild($name);\n} catch (NotFound $e) {\n    $federationCache->prune($name); // card gone server-side: drop the cached URI\n    return null;\n}","preventionTips":["Re-sync the system addressbook instead of trusting cached card URIs","Build card URIs exactly like SyncService::getCardUri() does","Treat federation 404s as authoritative removals, never as transient errors"],"tags":["carddav","dav","federation","http-404"],"backgroundTag":"dav-404-not-found","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}