{"id":13706702,"url":"https://github.com/hrbrmstr/xattrs","last_synced_at":"2025-10-29T01:30:43.717Z","repository":{"id":141239023,"uuid":"135436398","full_name":"hrbrmstr/xattrs","owner":"hrbrmstr","description":"🗃 Work With Filesystem Object Extended Attributes — https://hrbrmstr.github.io/xattrs/index.html","archived":false,"fork":false,"pushed_at":"2019-08-23T14:45:19.000Z","size":106,"stargazers_count":20,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-01T20:51:14.414Z","etag":null,"topics":["r","r-cyber","rstats","xattr","xattr-support"],"latest_commit_sha":null,"homepage":"https://rud.is/b/2018/05/30/os-secrets-exposed-extracting-extended-file-attributes-and-exploring-hidden-download-urls-with-the-xattrs-package/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hrbrmstr.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-05-30T12:02:27.000Z","updated_at":"2024-03-24T08:30:43.000Z","dependencies_parsed_at":"2024-01-14T20:29:10.827Z","dependency_job_id":null,"html_url":"https://github.com/hrbrmstr/xattrs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrbrmstr%2Fxattrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrbrmstr%2Fxattrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrbrmstr%2Fxattrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrbrmstr%2Fxattrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hrbrmstr","download_url":"https://codeload.github.com/hrbrmstr/xattrs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238752414,"owners_count":19524757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["r","r-cyber","rstats","xattr","xattr-support"],"created_at":"2024-08-02T22:01:05.848Z","updated_at":"2025-10-29T01:30:43.245Z","avatar_url":"https://github.com/hrbrmstr.png","language":"C++","funding_links":[],"categories":["C++"],"sub_categories":[],"readme":"---\noutput: \n  rmarkdown::github_document:\n    df_print: kable\n---\n```{r pkg-knitr-opts, include=FALSE}\nhrbrpkghelpr::global_opts()\n```\n\n```{r badges, results='asis', echo=FALSE, cache=FALSE}\nhrbrpkghelpr::stinking_badges()\n```\n\n```{r description, results='asis', echo=FALSE, cache=FALSE}\nhrbrpkghelpr::yank_title_and_description()\n```\n\n## NOTE\n\nI don't think this will work on Windows.\n\n## What's Inside The Tin\n\nThe following functions are implemented:\n\n```{r ingredients, results='asis', echo=FALSE, cache=FALSE}\nhrbrpkghelpr::describe_ingredients()\n```\n\n## Installation\n\n```{r install-ex, results='asis', echo=FALSE, cache=FALSE}\nhrbrpkghelpr::install_block()\n```\n\n## Usage\n\n```{r usage, cache=FALSE}\nlibrary(xattrs)\nlibrary(tidyverse) # for printing\n\n# current verison\npackageVersion(\"xattrs\")\n\n```\n\n### Basic Operation\n\nExtended attributes seem to get stripped when R builds pkgs so until I can figure out an easy way not to do that, just find any file on your system that has an `@` next to the permissions string in an `ls -l` directory listing.\n\n```{r 1, eval=FALSE}\nsample_file \u003c- \"~/Downloads/AdvancedTechniquesWithAWSGlueETLJobs.pdf\"\n\nlist_xattrs(sample_file)\n## [1] \"com.apple.metadata:kMDItemWhereFroms\"\n## [2] \"com.apple.quarantine\"  \n\nget_xattr_size(sample_file, \"com.apple.metadata:kMDItemWhereFroms\")\n## 157\n```\n\nExtended attributes can be _anything_ so it makes alot of sense to work with the contents as a raw vector:\n\n```{r 2, eval=FALSE}\nget_xattr_raw(sample_file, \"com.apple.metadata:kMDItemWhereFroms\")\n##   [1] 62 70 6c 69 73 74 30 30 a2 01 02 5f 10 6b 68 74 74 70 73 3a 2f\n##  [22] 2f 66 69 6c 65 73 2e 73 6c 61 63 6b 2e 63 6f 6d 2f 66 69 6c 65\n##  [43] 73 2d 70 72 69 2f 54 33 56 31 5a 44 51 48 4d 2d 46 44 4a 47 59\n##  [64] 4b 57 4a 33 2f 64 6f 77 6e 6c 6f 61 64 2f 61 64 76 61 6e 63 65\n##  [85] 64 74 65 63 68 6e 69 71 75 65 73 77 69 74 68 61 77 73 67 6c 75\n## [106] 65 65 74 6c 6a 6f 62 73 5f 5f 31 5f 2e 70 64 66 50 08 0b 79 00\n## [127] 00 00 00 00 00 01 01 00 00 00 00 00 00 00 03 00 00 00 00 00 00\n## [148] 00 00 00 00 00 00 00 00 00 7a\n```\n\nThere is a \"string\" version of the function, but it may return \"nothing\" if there are embedded NULLs or other breaking characters in the contents:\n\n```{r 3, eval=FALSE}\nget_xattr(sample_file, \"com.apple.metadata:kMDItemWhereFroms\")\n## [1] \"bplist00\\xa2\\001\\002_\\020khttps://files.slack.com/files-pri/T3V1ZDQHM-FDJGYKWJ3/download/advancedtechniqueswithawsglueetljobs__1_.pdfP\\b\\vy\"\n```\n\nYou are really better off doing this if you really want a raw string conversion:\n\n```{r 4, eval=FALSE}\nreadBin(get_xattr_raw(sample_file, \"com.apple.metadata:kMDItemWhereFroms\"), \"character\")\n## [1] \"bplist00\\xa2\\001\\002_\\020khttps://files.slack.com/files-pri/T3V1ZDQHM-FDJGYKWJ3/download/advancedtechniqueswithawsglueetljobs__1_.pdfP\\b\\vy\"\n```\n\nMore often than not (on macOS) extended attributes are \"binary property lists\" (or \"binary plist\" for short). You can test to see if the returned raw vector is likely a binary plist:\n\n```{r 5, eval=FALSE}\nis_bplist(get_xattr_raw(sample_file, \"com.apple.metadata:kMDItemWhereFroms\"))\n## [1] TRUE\n```\n\nIf it is, you can get the data out of it. For now, this makes a system call to `plutil` on macOS and `plistutil` on other systems. You'll be given a hint on how to install `plistutil` if it's not found.\n\n```{r 6, eval=FALSE}\nread_bplist(get_xattr_raw(sample_file, \"com.apple.metadata:kMDItemWhereFroms\"))\n## $plist\n## $plist$array\n## $plist$array$string\n## $plist$array$string[[1]]\n## [1] \"https://files.slack.com/files-pri/T3V1ZDQHM-FDJGYKWJ3/download/advancedtechniqueswithawsglueetljobs__1_.pdf\"\n## \n## \n## $plist$array$string\n## list()\n## \n## \n## attr(,\"version\")\n## [1] \"1.0\"\n```\n\nThis is R, so you should really consider doing this instead of any of the above #rectanglesrule:\n\n```{r 7, eval=FALSE}\nget_xattr_df(sample_file)\n## # A tibble: 2 x 3\n##   name                                  size contents   \n##   \u003cchr\u003e                                \u003cdbl\u003e \u003clist\u003e     \n## 1 com.apple.metadata:kMDItemWhereFroms   157 \u003craw [157]\u003e\n## 2 com.apple.quarantine                    56 \u003craw [56]\u003e \n```\n\nyou can live dangerously even with data frames, tho:\n\n```{r 8, eval=FALSE}\nget_xattr_df(sample_file) %\u003e% \n  mutate(txt = map_chr(contents, readBin, \"character\")) # potentially \"dangerous\"\n## # A tibble: 2 x 4\n##   name                 size contents  txt                            \n##   \u003cchr\u003e               \u003cdbl\u003e \u003clist\u003e    \u003cchr\u003e                          \n## 1 com.apple.metadata…   157 \u003craw [15… \"bplist00\\xa2\\x01\\x02_\\x10khtt…\n## 2 com.apple.quaranti…    56 \u003craw [56… 0083;5bc9c8ca;Slack;44698902-A…\n```\n\n### Extended Example\n\nWe can process a whole directory of files to see what extended attributes various path targets have:\n\n```{r 9, eval=FALSE}\nfils \u003c- list.files(\"~/Downloads\", full.names = TRUE) \n\nxdf \u003c- map_df(set_names(fils, fils), get_xattr_df, .id=\"path\")\n\ncount(xdf, name, sort=TRUE) \n## # A tibble: 11 x 2\n##    name                                                     n\n##    \u003cchr\u003e                                                \u003cint\u003e\n##  1 com.apple.quarantine                                    55\n##  2 com.apple.metadata:kMDItemWhereFroms                    46\n##  3 com.apple.lastuseddate#PS                               13\n##  4 com.apple.metadata:_kMDItemUserTags                      3\n##  5 com.apple.metadata:kMDItemDownloadedDate                 2\n##  6 com.apple.diskimages.fsck                                1\n##  7 com.apple.diskimages.recentcksum                         1\n##  8 com.apple.FinderInfo                                     1\n##  9 com.apple.metadata:com_apple_mail_dateReceived           1\n## 10 com.apple.metadata:com_apple_mail_dateSent               1\n## 11 com.apple.metadata:com_apple_mail_isRemoteAttachment     1\n```\n\nAnd we can work with `com.apple.metadata:kMDItemWhereFroms` binary plist data in bulk:\n\n```{r 10, eval=FALSE}\nfilter(xdf, name == \"com.apple.metadata:kMDItemWhereFroms\") %\u003e%\n  filter(map_lgl(contents, is_bplist)) %\u003e% \n  mutate(converted = map(contents, read_bplist)) %\u003e% \n  select(size, converted) %\u003e% \n  mutate(converted = map(converted, ~flatten_chr(.x$plist$array$string))) %\u003e% \n  unnest() %\u003e% \n  mutate(converted = urltools::domain(converted)) # you don't rly need to see the full URLs for this example\n## # A tibble: 44 x 2\n##     size converted                                              \n##    \u003cdbl\u003e \u003cchr\u003e                                                  \n##  1   189 dl.packetstormsecurity.net                             \n##  2   129 files.slack.com                                        \n##  3   120 files.slack.com                                        \n##  4   117 files.slack.com                                        \n##  5   592 irma.nps.gov                                           \n##  6   157 files.slack.com                                        \n##  7   624 github-production-release-asset-2e65be.s3.amazonaws.com\n##  8   197 images.unsplash.com                                    \n##  9   197 images.unsplash.com                                    \n## 10   185 files.slack.com                                        \n## # … with 34 more rows\n```\n\n### Full Suite\n\n```{r full-suite}\n# Create a temp file for the example\ntf \u003c- tempfile(fileext = \".csv\")\nwrite.csv(mtcars, tf)\n\n# has attributes? (shld be FALSE)\nhas_xattrs(tf)\nget_xattr(tf, \"is.rud.setting\")\n\n# set an attribute\nset_xattr(tf, \"is.rud.setting.a\", \"first attribut\")\nget_xattr(tf, \"is.rud.setting.a\")\nget_xattr_size(tf, \"is.rud.setting.a\")\n\n# shld be TRUE\nhas_xattrs(tf)\n\nset_xattr(tf, \"is.rud.setting.b\", \"second attribute\")\nget_xattr(tf, \"is.rud.setting.b\")\nget_xattr_size(tf, \"is.rud.setting.b\")\n\n# overwrite an attribute\nset_xattr(tf, \"is.rud.setting.a\", \"first attribute\")\nget_xattr(tf, \"is.rud.setting.a\")\nget_xattr_size(tf, \"is.rud.setting.a\")\n\n# see all the attributes\nlist_xattrs(tf)\n\n# data frame vs individual functions\nget_xattr_df(tf)\n\n# remove attribute\nrm_xattr(tf, \"is.rud.setting\")\nget_xattr(tf, \"is.rud.setting\")\n\n# cleanup\nunlink(tf)\n```\n\n## xattrs Metrics\n\n```{r cloc, echo=FALSE}\ncloc::cloc_pkg_md()\n```\n\n## Code of Conduct\n\nPlease note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhrbrmstr%2Fxattrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhrbrmstr%2Fxattrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhrbrmstr%2Fxattrs/lists"}