rules: - name: NPM Access Token (fine-grained) id: kingfisher.npm.1 pattern: | (?xi) \b ( npm_(?P[A-Za-z0-9]{30})(?P[A-Za-z0-9]{6}) ) \b pattern_requirements: min_digits: 2 checksum: actual: template: "{{ checksum }}" requires_capture: checksum expected: "{{ body | crc32 | base62: 6 }}" skip_if_missing: true references: - https://docs.npmjs.com/about-access-tokens - https://github.com/github/roadmap/issues/557 - https://github.blog/changelog/2022-12-06-limit-scope-of-npm-tokens-with-the-new-granular-access-tokens/ min_entropy: 3.3 confidence: medium examples: - "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" validation: type: Http content: request: headers: Authorization: Bearer {{ TOKEN }} method: GET response_matcher: - report_response: true - type: StatusMatch status: [200] - type: WordMatch words: ['"username":'] url: https://registry.npmjs.org/-/whoami revocation: type: HttpMultiStep content: steps: # Step 1: List all tokens and find the key for the matching token. # The response contains truncated tokens like "npm_rmll...aQIU". # We use a Regex with a Liquid-rendered prefix to locate the correct entry. - name: lookup_token_id request: method: GET url: https://registry.npmjs.org/-/npm/v1/tokens headers: Authorization: "Bearer {{ TOKEN }}" Accept: application/json response_matcher: - type: StatusMatch status: [200] - type: JsonValid extract: TOKEN_KEY: type: Regex pattern: '"key":"([^"]+)","token":"{{ TOKEN | prefix: 8 }}' # Step 2: Revoke the token using its key - name: revoke_token request: method: DELETE url: https://registry.npmjs.org/-/npm/v1/tokens/token/{{ TOKEN_KEY }} headers: Authorization: "Bearer {{ TOKEN }}" response_matcher: - report_response: true - type: StatusMatch status: [200, 204] - name: NPM Access Token (old format) id: kingfisher.npm.2 pattern: | (?xi) (?:_authToken|NPM_TOKEN) (?:.|[\n\r]){0,16}? ( [0-9A-F]{8} (?:-[0-9A-F]{4}){3} -[0-9A-F]{12} ) \b min_entropy: 3.5 confidence: medium examples: - '"_authToken": "b98ec224-cdb2-4340-b7bd-9617fc719d1d"' # kingfisher:ignore - '-export NPM_TOKEN="007e64c7-635d-4d54-8295-f364cd8e0e0f"' # kingfisher:ignore validation: type: Http content: request: headers: Authorization: Bearer {{ TOKEN }} method: GET response_matcher: - report_response: true - type: StatusMatch status: [200] - type: WordMatch words: ['"username":'] url: https://registry.npmjs.org/-/whoami revocation: type: HttpMultiStep content: steps: # Step 1: List all tokens and find the key for the matching token. # Old-format tokens are UUIDs; the response truncates them too. - name: lookup_token_id request: method: GET url: https://registry.npmjs.org/-/npm/v1/tokens headers: Authorization: "Bearer {{ TOKEN }}" Accept: application/json response_matcher: - type: StatusMatch status: [200] - type: JsonValid extract: TOKEN_KEY: type: Regex pattern: '"key":"([^"]+)","token":"{{ TOKEN | prefix: 8 }}' # Step 2: Revoke the token using its key - name: revoke_token request: method: DELETE url: https://registry.npmjs.org/-/npm/v1/tokens/token/{{ TOKEN_KEY }} headers: Authorization: "Bearer {{ TOKEN }}" response_matcher: - report_response: true - type: StatusMatch status: [200, 204]