{"id":36824001,"url":"https://github.com/justenwalker/mack","last_synced_at":"2026-01-12T14:03:00.131Z","repository":{"id":248473959,"uuid":"796700064","full_name":"justenwalker/mack","owner":"justenwalker","description":"A Go implementation of Macaroons - Cookies with Contextual Caveats","archived":false,"fork":false,"pushed_at":"2025-02-18T02:58:21.000Z","size":86,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-18T03:31:43.682Z","etag":null,"topics":["authorization","golang","macaroons","security"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/justenwalker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-06T13:13:47.000Z","updated_at":"2025-02-18T02:58:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"57199ae8-de71-4db2-bb12-42a389ff448b","html_url":"https://github.com/justenwalker/mack","commit_stats":null,"previous_names":["justenwalker/mack"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/justenwalker/mack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justenwalker%2Fmack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justenwalker%2Fmack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justenwalker%2Fmack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justenwalker%2Fmack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/justenwalker","download_url":"https://codeload.github.com/justenwalker/mack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justenwalker%2Fmack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28340235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["authorization","golang","macaroons","security"],"created_at":"2026-01-12T14:02:59.964Z","updated_at":"2026-01-12T14:03:00.110Z","avatar_url":"https://github.com/justenwalker.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mack - A Go library for interacting with Macaroons\n\n## What is this library\n\nCurrently,\n\n### It is:\n\n1. Something I wrote up in my spare time to understand Macaroons a bit better.\n2. A learning exercise for me to explore library design ideas.\n3. A hobby/side project for me to obsess over.\n\n### It is **not**:\n\n1. A production-grade library you should bet your entire business and information security on.\n2. An official or reference implementation of Macaroons. \n3. Able to make Margaritas.\n\nThat said, I'd welcome some advice, comments, constructive feedback about the library, it's ergonomics, or rough edges. I'm also not a cryptographer or security researcher, so if there are any flaws in the implementation I'd like to know about them.\n\n### Why?\n\nThe Macaroons paper specifies how to create this chained list of caveats, but doesn't have any opinions. Unlike JWT, there are really no standards what caveats are, or how to validate them. This library implements the base methods for \nconstructing and verifying macaroons and exposes interfaces for implementing the areas of the spec that are open to \ninterpretation, while also providing some opinionated implementations of these interfaces to make it actually useful.\n\n### Full Example\n\nAn example implementation using this library is in the [example](./example) directory.\nThis covers constructing a Macaroon, discharging, validating, and clearing caveats.\n\n## What is a Macaroon?\n\n[See Paper](https://www.ndss-symposium.org/wp-content/uploads/2017/09/04_3_1.pdf)\n\nA Macaroon is a security credential that support decentralized delegation.\nIt is implemented as a chain of signed \"Caveats\".\n\nMacaroons are similar to [JWT](https://jwt.io) in that they are Bearer tokens that grant access to a resource. Where they differ is that, JWTs are minted by a token service with a fixed set of \"**claims\"** that cannot be altered; whereas as Macaroon may derive new sub-macaroons with more limited permissions, without coordinating with the service issuing the macaroon initially.\n\nA **\"caveat\"** is different from a JWT claim. While a claim is some (authenticated) information about whom the bearer is, or what the bearer be able to do or what permissions they have, what groups they are from etc... a caveat only exists as a predicate on what the bearer is allowed to do: it limits the conditions under which the macaroon is valid.\n\nFor example, a list of claims may be:\n\n```json\n{\n  \"user\": \"foo\",\n  \"groups\": [\"g1\",\"g2\"],\n  \"permissions\": [\"p1\",\"p2\"]\n}\n```\n\nWhereas a list of caveats might be:\n\n- `request.path =~ \"/user/.*\"`\n- `org in (org1,org2)`\n- `app not in (sensitive_app)`\n\nNOTE: some conventional JWT claims can be interpreted as caveats:\n- `iss`: token was issuer by this issuer, to limit which token sources should be trusted.\n- `aud`: token is valid for a specific audience, preventing token from `service-a` from being used on `service-b`\n- `nbf`: token is not valid before this unix timestamp\n- `exp`: token is no longer valid after this date\n\n## Quick Tour\n\n### Packages\n\n- `mack` - The main package. These are where all the Macaroon primitive types and operations reside.\n- `sensible` - Provides sensible default implementations of cryptographic functions.\n- `thirdparty` - Provides a framework for constructing third-party caveats and discharging them.\n- `thirdparty/exchange` - Implements interfaces in `thirdparty` by using encrypted caveat ids.\n\n### Create a Macaroon Scheme\n\nFirst, you must create a `mack.Scheme`.\n\nYou can create a new scheme with:\n\n```go\nscheme := mack.NewScheme(mack.SchemeConfig{\n\tHMACScheme:           hms,\n\tEncryptionScheme:     es,\n\tBindForRequestScheme: b4rs,\n})\n```\n\nEach configuration option is an interface implementing a specific cryptographic algorithm that will be part of the scheme.\n\n- `HMACScheme` - Implements the algorithm is used to generate HMACs.\n- `EncryptionScheme` - Implements functions used for Encryption/Decryption for Third-Party Caveats (HMAC Size and Encryption Key size must match!)\n- `BindForRequestScheme` - Implements the function used to bind a discharge macaroon to an authorization macaroon.\n\n### Sensible Defaults\n\nThere is a `sensible` package can be used for creating a `mack.Scheme` with sensible defaults:\n\n- `HMACScheme`: HMAC-SHA256\n- `EncryptionScheme`: AES-256-GCM with Random  96-bit Nonce\n- `BindForRequestScheme`: discharge.Sig = `HMAC-SHA256(Auth.Sig, Discharge.Sig)`\n\n### Create a Macaroon\n\nNew Macaroons can be constructed from the Scheme using the `NewMacaroon` function:\n\n```go\n// start with a scheme, in this case a sensible default\nscheme := sensible.Scheme()\n\t\n// Macaroon ID (nonce): Should be random, and never used again.\n// - UUID might be a good choice.\nid := make([]byte, 16)\nrand.Read(id)\n\n// Macaroon Root Key:\n// You have to be able to associate the Macaroon ID with this key somehow.\n// Perhaps you store the id/key pair in a DB or derive a key from a pre-shared password and the macaroon id.\nkey := make([]byte, scheme.KeySize())\nrand.Read(key)\n\n// Macaroon Caveats:\n// Assemble the list of initial caveats.\n// You should always have an initial set of at least 1 caveat on a macaroon.\n// Without a caveat, a macaroon is permitted to do anything.\n// Such a macaroon can be constructed with Scheme.UnsafeRootMacaroon, but this is not recommended.\ncaveats = [][]byte{\n    []byte(`org = Organization`),\n    []byte(`user = User`),\n}\n\n// Creates the initial macaroon.\nm, err := scheme.NewMacaroon(\"https://www.example.com\", id, key, caveats...)\n```\n\n### Add First-Party Caveats\n\nFirst-party caveats are caveats that are cleared by the authorizing service.\n\nThe caveat ID is typically a predicate that describes a condition that must be true\nif the macaroon is used. How it is parsed, and evaluated is up to the authorizing service, \nso what the bytes represent is opaque to the macaroon scheme.\n\nAdditional caveats may be added to a macaroon using `Scheme.AddFirstPartyCaveat` \n\n```go\nm, err = scheme.AddFirstPartyCaveat(\u0026m, []byte(`expires = 2006-01-02`))\n```\n\n### Validating Macaroon Stacks\n\nAn Authorizing Macaroon and its associated Discharge Macaroons constitute a `macaroon.Stack`.\nClients construct this stack by receiving discharge macaroons from third-parties for all of their third party caveats\nand presenting both the authorizing macaroon and all discharge macaroons bound to it to the Authorizing Service.\n\nClients create a stack by using `Scheme.PrepareStack`.\n\n```go\nstack, err := scheme.PrepareStack(authorizingMacaroon, dischargeMacaroons)\n```\n\nThe stack is then transmitted with the request. How the stack is encoded is implementation specific, so see [example](./example) for implementation.\n\nAfter encoding the stack into bytes, it can be put into a request body or encoded as Base64 and added to an HTTP Authorization\nheader. Whatever the service expects.\n\nThe Service, after receiving this stack, should decode it.\n\nValidation on the server side happens in two phases: Verifying, and Clearing.\n\nStack *Verification* only ensures that all the cryptographic signatures match their expected values,\nbut it doesn't say anything about the validity of the caveats on the macaroon.\n\n```go\nverifiedStack, err := scheme.Verify(ctx, key, stack)\n```\n\nThe key in the above verification function is extracted from the Root Macaroon ID. As an example: this could be by looking it up\nin a database, or by combining the id with some shared secret via an HMAC function to generate the key.\n\nAfter getting a `VerifiedStack` from the `Scheme.Verify` function, the VerifiedStack should be cleared before\nallowing the action:\n\n```go\n// checker is a PredicateChecker.\n// A PredicateChecker interprets a caveat id, and evaluates its result. \n// It returns true if the predicate is satisfied.\nif err := verifiedStack.Clear(ctx, checker); err != nil {\n\t// stack failed to clear\n\treturn err\n}\n// stack is verified, proceed with action\n// ...\n```\n\n### Add Third-Party Caveats to a Macaroon (Attenuation)\n\nWhile you can construct these values from scratch and use `scheme.AddThirdPartyCaveat`, the `thirdparty` package can help generate \nand apply these values to a Macaroon via a `thirdparty.Attenuator`.\n\n```go\ntpa, err := thirdparty.NewAttenuator(thirdparty.AttenuatorConfig{\n    Location:     \"https://thirdparty.example.com\",\n    Scheme:       scheme,\n    CaveatIssuer: caveatIssuer,\n})\n```\n\n- `Location` will be added to each caveat to hint at where to discharge the macaroon later.\n- `Scheme` should be provided and match the scheme used to create the macaroon. Mixing schemes will result in undefined behavior.\n- `CaveatIDIssuer` issues third party caveat IDs based on a generated caveat key and a predicate evaluated by the third party.\n\n### Caveat ID Issuer\n\nCaveatIDIssuer is an interface that must be implemented to construct a `thirdparty.Attenuator`. \nIt exchanges a `thirdparty.Ticket` which is a pair containing a CaveatKey that is randomly generated for this \nmacaroon, and a Predicate to be evaluated by the third party before discharges the caveat; for an opaque caveat ID \nthat only the third party can use later to recover the Caveat Key and Predicate.\n\nOne way to implement this is by having a third-party implement an API that can take this `thirdparty.Ticket` and \nreturn a caveat id. This requires the third party to be active in minting a caveat, and typically would create an cId/cK.\nImplementing such a protocol is out of scope for this library, but another library implementing\n`thirdparty.CaveatIDIssuer` may provide it.\n\nAnother way is to use public-key cryptography to encrypt the caveat key/predicate payload. \nThis doesn't require a third party to be an active participant in the creation of the caveat.\nInstead, the Caveat ID is constructed by encrypting the Caveat Key and Predicate using the third party's public key.\n\nThis method implemented in the `exchange` package which can be configured with Encoder/Encryptor implementations for \nwhich the third party discharge service has a corresponding implementation for Decoder/Decryptor.\n\n```go\nissuer := exchange.CaveatIDIssuer{\n    Encryptor: encryptor,\n    Encoder:   encoder,\n}\n```\n\n```mermaid\nflowchart TD\n    subgraph Add 3p Caveat\n    M1[/Macaroon/]\n    PR[/3rd Party Predicate/]\n    M1 ~~~ PR\n    M1 ~~~ M2\n    end\n    subgraph Attenuator\n        PR --\u003e T[/Ticket/]\n        RK[/Random Source/] -- Random Data --\u003e CK\n        CK[/Caveat Key/] --\u003e T\n        CID[/Caveat ID/]\n        ECK(Encrypt Caveat Key)\n        CK --\u003e ECK\n        M1 -- sig --\u003e ECK\n        T --\u003e E\n        ECK --\u003e VID[/Verification ID/]\n        ECK ~~~ CID\n        subgraph CaveatIDIssuer\n            E[Encoder] -- encoded ticket --\u003e C\n            C[Encryptor] -- encrypted bytes --\u003e CID\n        end\n        VID --\u003e APPEND\n        CID --\u003e APPEND\n        APPEND(Append Caveat) --\u003e M2\n        M2[/New Macaroon/]\n    end\n```\n \nPossible implementations for the `Encoder` interface:\n- [example/msgpack](./example/msgpack)- Encodes using [MsgPack](https://msgpack.org/index.html)\n\nA possible implementation for the Encryptor/Decryptor:\n- [example/agecrypt](./example/agecrypt): uses [Age](https://age-encryption.org/) to encrypt third-party caveat IDs using Age Recipient.\n\n\n## Requesting a Discharge Macaroon from a Third Party\n\nThe means by which requests are made to a third party service to create discharge macaroons are not defined\nby the spec, but implemented by the end user. This library has some helper to make it easier to discharge\nall third-party caveats recursively, returning a collection of discharge macaroons.\n\nAs long as the behavior of the discharge request can be described using the ThirdParty interface,\nthey can be collected into a `thirdparty.Set` which has a Discharge method taking a Macaroon and returning all\nthe discharge macaroons.\n\n```go\nthirdPartySet := thirdparty.Set{authThirdParty}\ndischargeMacaroons, err := thirdPartySet.Discharge(ctx, \u0026myMacaroon)\n```\n\n## Discharging Third-Party Caveats\n\nWhen a Macaroon has third party caveats, they must be discharged to validate the entire macaroon stack.\nThe bearer of the authorization macaroon should make a request to the third-party service with the caveat ID to discharge\n(likely with some authorization for that service too.)\n\nA third-party service may use the `thirdparty.Discharger` to take a CaveatID and return a new Macaroon which will discharge the caveat.\n\n```go\ndischarger, err := thirdparty.NewDischarger(thirdparty.DischargerConfig{\n    Location:         \"https://thirdparty.example.com\",\n    Scheme:           scheme,\n    TicketExtractor:  text,\n})\n```\n\n- `Location` will be added to the discharge macaroon.\n- `Scheme` should be provided and match the scheme used to create the macaroon. Mixing schemes will result in undefined behavior.\n- `TicketExtractor` extracts `thirdparty.Ticket` from a caveat ID.\n\n### TicketExtractor\n\nA TicketExtractor extracts the `thirdparty.Ticket` information from a caveat ID. \nThis is the \"dual\" of the `thirdparty.CaveatIDIssuer`\n\nIf the `CaveatIDIssuer` resulted in the third party generating an opaque caveat id and associating it \nto a `thirdparty.Ticket` database, then this would look up that ticket and return it. Implementing such \na protocol is out of scope for this library, but another library implementing `thirdparty.TicketExtractor` may provide it.\n\nIf the CaveatIDIssue instead encrypted the ticket for the third party using its public key, then\nthis would extract the ticket from the caveat id by decrypting and decoding it.\n\nThis method implemented in the `exchange` package which can be configured with Decoder/Decryptor.\n\n```go\ntext := exchange.TicketExtractor{\n    Decryptor: decryptor,\n    Decoder:   decoder,\n}\n```\n\n```mermaid\nflowchart TD\n    subgraph Discharger\n        S((START)) --\u003e CID\n        CID[/Third-party Caveat ID/] --\u003e D[Decryptor]\n        subgraph TicketExtractor\n            D -- decrypted bytes --\u003e DEC[Decoder]\n            DEC -- decoded ticket --\u003e T[Ticket]\n        end\n        T --\u003e P[/Predicate/]\n        T --\u003e CK[/Cavate Key/]\n        P --\u003e PC{Predicate Checker}\n        PC -- ok --\u003e CDM(Create Discharge Macaroon)\n        PC -- fail --\u003e E((ERROR))\n        CDM --\u003e DCM[/Discharge Macaroon/]\n        CID --\u003e DCM\n        CK --\u003e DCM\n        DCM --\u003e END((END))\n    end\n```\n\nPossible implementations for the `Decoder` interface:\n- [example/msgpack](./example/msgpack) - Encodes using [MsgPack](https://msgpack.org/index.html)\n\nA possible implementation for the `Decryptor`:\n- [example/agecrypt](./example/agecrypt): uses [Age](https://age-encryption.org/) to decrypt caveat IDs using Age Identities.\n\n### PredicateChecker\n\nA PredicateChecker interprets a caveat id, and evaluates its result. It returns true if the predicate is satisfied.\n\nOnce satisfied, the `thirdparty.Discharger` can issue the discharge caveat from the ticket. \nThe implementation of the `thirdparty.PredicateChecker` is provided by the user of this library, since\na caveat id is an opaque string of bytes, without any meaning in the macaroon spec.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustenwalker%2Fmack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjustenwalker%2Fmack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustenwalker%2Fmack/lists"}