{"action":"created","comment":{"url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/comments/174011709","pull_request_review_id":103292213,"id":174011709,"diff_hunk":"@@ -0,0 +1,305 @@\n+/**\n+ * @fileoverview Require scripts and styles to use Subresource Integrity\n+ */\n+\n+import * as crypto from 'crypto';\n+import { URL } from 'url';\n+import { promisify } from 'util';\n+\n+import * as async from 'async';\n+\n+import { Category } from 'sonarwhal/dist/src/lib/enums/category';\n+import { RuleContext } from 'sonarwhal/dist/src/lib/rule-context';\n+// The list of types depends on the events you want to capture.\n+import { IRule, RuleMetadata, FetchEnd } from 'sonarwhal/dist/src/lib/types';\n+import { debug as d } from 'sonarwhal/dist/src/lib/utils/debug';\n+import { RuleScope } from 'sonarwhal/dist/src/lib/enums/rulescope';\n+\n+const debug: debug.IDebugger = d(__filename);\n+const everySeries = promisify(async.everySeries);\n+\n+/*\n+ * ------------------------------------------------------------------------------\n+ * Public\n+ * ------------------------------------------------------------------------------\n+ */\n+\n+// We don't do a `const enum` because of this: https://stackoverflow.com/questions/18111657/how-does-one-get-the-names-of-typescript-enum-entries#comment52596297_18112157\n+enum algorithms {\n+    sha256 = 1,\n+    sha384 = 2,\n+    sha512 = 3\n+}\n+\n+export default class SRIRule implements IRule {\n+\n+    public static readonly meta: RuleMetadata = {\n+        docs: {\n+            category: Category.security,\n+            description: `Require scripts and link elements to use Subresource Integrity`\n+        },\n+        id: 'sri',\n+        schema: [{\n+            additionalProperties: false,\n+            properties: {\n+                baseline: {\n+                    oneOf: [Object.keys(algorithms)],\n+                    type: 'string'\n+                }\n+            }\n+        }],\n+        scope: RuleScope.any\n+    }\n+\n+    private resources: Map<string, string>;\n+    private context: RuleContext;\n+    private origin: string;\n+    private baseline: string = 'sha384';\n+\n+    /**\n+     * Returns the hash of the content for the given `sha` strengh in a format\n+     * valid with SRI:\n+     * * base64\n+     * * `sha384-hash`\n+     */\n+    private calculateHash(content: string, sha): string {\n+        const hash = crypto\n+            .createHash(sha)\n+            .update(content)\n+            .digest('base64');\n+\n+        return hash;\n+    }\n+\n+    /**\n+     * Checks if the element that originated the request/response is a script or a stylesheet.\n+     * There could be other downloads from a `link` element that are not stylesheets and should\n+     * be ignored.\n+     */\n+    private isScriptOrLink(evt: FetchEnd): Promise<boolean> {\n+        debug('Is <script> or <link>?');\n+        const { element } = evt;\n+\n+        /*\n+         * We subscribe to `fetch::end::script|css`, so element should always exist.\n+         * \"that should never happen\" is the fastest way to make it happen so better be safe\n+         */\n+        /* istanbul ignore if */\n+        if (!element) {\n+            return Promise.resolve(false);\n+        }\n+\n+        /*\n+         * The element is not one that we care about (could be an img, video, etc.)\n+         * No need to report anything, but we can stop processing things right away.\n+         */\n+        const isScript: boolean = element.nodeName === 'SCRIPT' && !!element.getAttribute('src');\n+        const isStyle: boolean = element.nodeName === 'LINK' && element.getAttribute('rel') === 'stylesheet';\n+\n+        return Promise.resolve(isScript || isStyle);\n+    }\n+\n+    /**\n+     * Verifies if the response is elegible for integrity validation. I.E.:\n+     *\n+     * * `same-origin`\n+     * * `cross-origin` on a CORS-enabled request\n+     *\n+     * More info in https://w3c.github.io/webappsec-subresource-integrity/#is-response-eligible\n+     */\n+    private async isEligibleForIntegrityValidation(evt: FetchEnd): Promise<boolean> {\n+        debug('Is eligible for integrity validation?');\n+\n+        const { element, resource } = evt;\n+        const resourceOrigin: string = new URL(resource).origin;\n+\n+        // CORS validation only applies to scripts, styles are OK\n+        if (element.nodeName !== 'SCRIPT') {\n+            return true;\n+        }\n+\n+        if (this.origin === resourceOrigin) {\n+            return true;\n+        }\n+\n+        // cross-origin scripts need to be loaded with a valid \"crossorigin\" attribute (ie.: anonymous or use-credentials)\n+        const crossorigin = element.getAttribute('crossorigin');\n+\n+        if (!crossorigin) {\n+            await this.context.report(resource, element, `Cross-origin scripts need a \"crossorigin\" attribute to be elegible for integrity validation`);\n+\n+            return false;\n+        }\n+\n+        const validCrossorigin = crossorigin === 'anonymous' || crossorigin === 'use-credentials';\n+\n+        if (!validCrossorigin) {\n+            await this.context.report(resource, element, `Attribute \"crossorigin\" doesn't have a valid value, should \"anonymous\" or \"use-credentials\": crossorigin=\"${crossorigin}\"`);\n+        }\n+\n+        return validCrossorigin;\n+    }\n+\n+    /** Checks if the element that triggered the download has the `integrity` attribute. */\n+    private async hasIntegrityAttribute(evt: FetchEnd): Promise<boolean> {\n+        debug('has integrity attribute?');\n+        const { element, resource } = evt;\n+        const integrity = element.getAttribute('integrity');\n+\n+        if (!integrity) {\n+            await this.context.report(resource, element, `Resource ${resource} requested without the \"integrity\" attribute`);\n+        }\n+\n+        return !!integrity;\n+    }\n+\n+    /**\n+     * Checks if the format of the `integrity` attribute is valid and if the used hash meets\n+     * the baseline (by default sha-384). In the case of multiple algorithms used, the\n+     * one with the highest priority is the used one to validate. E.g.:\n+     *\n+     * * `<script src=\"https://example.com/example-framework.js\"\n+     *   integrity=\"sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7\"\n+     *   crossorigin=\"anonymous\"></script>`\n+     * * `<script src=\"https://example.com/example-framework.js\"\n+     *   integrity=\"sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7\n+     *              sha384-+/M6kredJcxdsqkczBUjMLvqyHb1K/JThDXWsBVxMEeZHEaMKEOEct339VItX1zB\"\n+     *   crossorigin=\"anonymous\"></script>`\n+     *\n+     * https://w3c.github.io/webappsec-subresource-integrity/#agility\n+     */\n+    private async isIntegrityFormatValid(evt: FetchEnd): Promise<boolean> {\n+        debug('Is integrity attribute valid?');\n+        const { element, resource } = evt;\n+        const integrity = element.getAttribute('integrity');\n+        const integrityRegExp = /^sha(256|384|512)-/;","path":"packages/rule-sri/src/sri.ts","position":null,"original_position":175,"commit_id":"b6fb4af76fba5b120c7e7baed08a3a31477e8fe2","original_commit_id":"8f7ee51aa76a3ddc9cb329f7e2c091673009a0e3","user":{"login":"molant","id":606594,"avatar_url":"https://avatars0.githubusercontent.com/u/606594?v=4","gravatar_id":"","url":"https://api.github.com/users/molant","html_url":"https://github.com/molant","followers_url":"https://api.github.com/users/molant/followers","following_url":"https://api.github.com/users/molant/following{/other_user}","gists_url":"https://api.github.com/users/molant/gists{/gist_id}","starred_url":"https://api.github.com/users/molant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/molant/subscriptions","organizations_url":"https://api.github.com/users/molant/orgs","repos_url":"https://api.github.com/users/molant/repos","events_url":"https://api.github.com/users/molant/events{/privacy}","received_events_url":"https://api.github.com/users/molant/received_events","type":"User","site_admin":false},"body":"I've added another test. Only the first instance is accessible via `getAttribute`. Additionally, it looks like Chrome tries to download the file twice if the first hash is invalid, but only the first attribute is accessible. I've left the code of the test commented with an explanation. ","created_at":"2018-03-13T04:03:10Z","updated_at":"2018-03-13T04:03:10Z","html_url":"https://github.com/sonarwhal/sonarwhal/pull/875#discussion_r174011709","pull_request_url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/comments/174011709"},"html":{"href":"https://github.com/sonarwhal/sonarwhal/pull/875#discussion_r174011709"},"pull_request":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875"}},"in_reply_to_id":173940324},"pull_request":{"url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875","id":174245862,"html_url":"https://github.com/sonarwhal/sonarwhal/pull/875","diff_url":"https://github.com/sonarwhal/sonarwhal/pull/875.diff","patch_url":"https://github.com/sonarwhal/sonarwhal/pull/875.patch","issue_url":"https://api.github.com/repos/sonarwhal/sonarwhal/issues/875","number":875,"state":"open","locked":false,"title":"New: Rule SRI","user":{"login":"molant","id":606594,"avatar_url":"https://avatars0.githubusercontent.com/u/606594?v=4","gravatar_id":"","url":"https://api.github.com/users/molant","html_url":"https://github.com/molant","followers_url":"https://api.github.com/users/molant/followers","following_url":"https://api.github.com/users/molant/following{/other_user}","gists_url":"https://api.github.com/users/molant/gists{/gist_id}","starred_url":"https://api.github.com/users/molant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/molant/subscriptions","organizations_url":"https://api.github.com/users/molant/orgs","repos_url":"https://api.github.com/users/molant/repos","events_url":"https://api.github.com/users/molant/events{/privacy}","received_events_url":"https://api.github.com/users/molant/received_events","type":"User","site_admin":false},"body":"<!--\r\n\r\nRead our pull request guide:\r\nhttps://sonarwhal.com/docs/contributor-guide/contributing/pull-requests/\r\n\r\nFor the following items put an \"x\" between the square brackets\r\n(i.e. [x]) if you completed the associated item.\r\n\r\n-->\r\n\r\n## Pull request checklist\r\n\r\nMake sure you:\r\n\r\n- [X] Signed the [Contributor License Agreement](https://cla.js.foundation/sonarwhal/sonarwhal)\r\n- [X] Followed the [commit message guidelines](https://sonarwhal.com/docs/contributor-guide/getting-started/pull-requests/#commit-messagess)\r\n\r\nFor non-trivial changes, please make sure you also:\r\n\r\n- [X] Added/Updated related documentation.\r\n- [X] Added/Updated related tests.\r\n\r\n## Short description of the change(s)\r\n\r\nClose #26\r\n\r\n<!--\r\n\r\nIf this is a non-trivial change, include information such as what\r\nbenefits this change brings as well as possible drawbacks.\r\n\r\nIf this fixes an existing issue, include the relavant issue number(s).\r\n\r\nThank you for taking the time to open this PR!\r\n\r\n-->\r\n","created_at":"2018-03-11T22:17:39Z","updated_at":"2018-03-13T04:03:10Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d5cdb0920e0d7d0cbd57cbe40d566d57f7e1ccb0","assignee":null,"assignees":[],"requested_reviewers":[{"login":"sarvaje","id":1581288,"avatar_url":"https://avatars0.githubusercontent.com/u/1581288?v=4","gravatar_id":"","url":"https://api.github.com/users/sarvaje","html_url":"https://github.com/sarvaje","followers_url":"https://api.github.com/users/sarvaje/followers","following_url":"https://api.github.com/users/sarvaje/following{/other_user}","gists_url":"https://api.github.com/users/sarvaje/gists{/gist_id}","starred_url":"https://api.github.com/users/sarvaje/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sarvaje/subscriptions","organizations_url":"https://api.github.com/users/sarvaje/orgs","repos_url":"https://api.github.com/users/sarvaje/repos","events_url":"https://api.github.com/users/sarvaje/events{/privacy}","received_events_url":"https://api.github.com/users/sarvaje/received_events","type":"User","site_admin":false},{"login":"austinkelleher","id":3771924,"avatar_url":"https://avatars0.githubusercontent.com/u/3771924?v=4","gravatar_id":"","url":"https://api.github.com/users/austinkelleher","html_url":"https://github.com/austinkelleher","followers_url":"https://api.github.com/users/austinkelleher/followers","following_url":"https://api.github.com/users/austinkelleher/following{/other_user}","gists_url":"https://api.github.com/users/austinkelleher/gists{/gist_id}","starred_url":"https://api.github.com/users/austinkelleher/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/austinkelleher/subscriptions","organizations_url":"https://api.github.com/users/austinkelleher/orgs","repos_url":"https://api.github.com/users/austinkelleher/repos","events_url":"https://api.github.com/users/austinkelleher/events{/privacy}","received_events_url":"https://api.github.com/users/austinkelleher/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875/commits","review_comments_url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875/comments","review_comment_url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sonarwhal/sonarwhal/issues/875/comments","statuses_url":"https://api.github.com/repos/sonarwhal/sonarwhal/statuses/b6fb4af76fba5b120c7e7baed08a3a31477e8fe2","head":{"label":"molant:rule-sri","ref":"rule-sri","sha":"b6fb4af76fba5b120c7e7baed08a3a31477e8fe2","user":{"login":"molant","id":606594,"avatar_url":"https://avatars0.githubusercontent.com/u/606594?v=4","gravatar_id":"","url":"https://api.github.com/users/molant","html_url":"https://github.com/molant","followers_url":"https://api.github.com/users/molant/followers","following_url":"https://api.github.com/users/molant/following{/other_user}","gists_url":"https://api.github.com/users/molant/gists{/gist_id}","starred_url":"https://api.github.com/users/molant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/molant/subscriptions","organizations_url":"https://api.github.com/users/molant/orgs","repos_url":"https://api.github.com/users/molant/repos","events_url":"https://api.github.com/users/molant/events{/privacy}","received_events_url":"https://api.github.com/users/molant/received_events","type":"User","site_admin":false},"repo":{"id":94958400,"name":"sonar","full_name":"molant/sonar","owner":{"login":"molant","id":606594,"avatar_url":"https://avatars0.githubusercontent.com/u/606594?v=4","gravatar_id":"","url":"https://api.github.com/users/molant","html_url":"https://github.com/molant","followers_url":"https://api.github.com/users/molant/followers","following_url":"https://api.github.com/users/molant/following{/other_user}","gists_url":"https://api.github.com/users/molant/gists{/gist_id}","starred_url":"https://api.github.com/users/molant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/molant/subscriptions","organizations_url":"https://api.github.com/users/molant/orgs","repos_url":"https://api.github.com/users/molant/repos","events_url":"https://api.github.com/users/molant/events{/privacy}","received_events_url":"https://api.github.com/users/molant/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/molant/sonar","description":null,"fork":true,"url":"https://api.github.com/repos/molant/sonar","forks_url":"https://api.github.com/repos/molant/sonar/forks","keys_url":"https://api.github.com/repos/molant/sonar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/molant/sonar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/molant/sonar/teams","hooks_url":"https://api.github.com/repos/molant/sonar/hooks","issue_events_url":"https://api.github.com/repos/molant/sonar/issues/events{/number}","events_url":"https://api.github.com/repos/molant/sonar/events","assignees_url":"https://api.github.com/repos/molant/sonar/assignees{/user}","branches_url":"https://api.github.com/repos/molant/sonar/branches{/branch}","tags_url":"https://api.github.com/repos/molant/sonar/tags","blobs_url":"https://api.github.com/repos/molant/sonar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/molant/sonar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/molant/sonar/git/refs{/sha}","trees_url":"https://api.github.com/repos/molant/sonar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/molant/sonar/statuses/{sha}","languages_url":"https://api.github.com/repos/molant/sonar/languages","stargazers_url":"https://api.github.com/repos/molant/sonar/stargazers","contributors_url":"https://api.github.com/repos/molant/sonar/contributors","subscribers_url":"https://api.github.com/repos/molant/sonar/subscribers","subscription_url":"https://api.github.com/repos/molant/sonar/subscription","commits_url":"https://api.github.com/repos/molant/sonar/commits{/sha}","git_commits_url":"https://api.github.com/repos/molant/sonar/git/commits{/sha}","comments_url":"https://api.github.com/repos/molant/sonar/comments{/number}","issue_comment_url":"https://api.github.com/repos/molant/sonar/issues/comments{/number}","contents_url":"https://api.github.com/repos/molant/sonar/contents/{+path}","compare_url":"https://api.github.com/repos/molant/sonar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/molant/sonar/merges","archive_url":"https://api.github.com/repos/molant/sonar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/molant/sonar/downloads","issues_url":"https://api.github.com/repos/molant/sonar/issues{/number}","pulls_url":"https://api.github.com/repos/molant/sonar/pulls{/number}","milestones_url":"https://api.github.com/repos/molant/sonar/milestones{/number}","notifications_url":"https://api.github.com/repos/molant/sonar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/molant/sonar/labels{/name}","releases_url":"https://api.github.com/repos/molant/sonar/releases{/id}","deployments_url":"https://api.github.com/repos/molant/sonar/deployments","created_at":"2017-06-21T03:20:07Z","updated_at":"2017-06-21T03:20:09Z","pushed_at":"2018-03-13T04:01:04Z","git_url":"git://github.com/molant/sonar.git","ssh_url":"git@github.com:molant/sonar.git","clone_url":"https://github.com/molant/sonar.git","svn_url":"https://github.com/molant/sonar","homepage":"https://sonarwhal.com/","size":5828,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"sonarwhal:master","ref":"master","sha":"43f395fcfe0bca655dcb1013f07a386811214191","user":{"login":"sonarwhal","id":25042266,"avatar_url":"https://avatars0.githubusercontent.com/u/25042266?v=4","gravatar_id":"","url":"https://api.github.com/users/sonarwhal","html_url":"https://github.com/sonarwhal","followers_url":"https://api.github.com/users/sonarwhal/followers","following_url":"https://api.github.com/users/sonarwhal/following{/other_user}","gists_url":"https://api.github.com/users/sonarwhal/gists{/gist_id}","starred_url":"https://api.github.com/users/sonarwhal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonarwhal/subscriptions","organizations_url":"https://api.github.com/users/sonarwhal/orgs","repos_url":"https://api.github.com/users/sonarwhal/repos","events_url":"https://api.github.com/users/sonarwhal/events{/privacy}","received_events_url":"https://api.github.com/users/sonarwhal/received_events","type":"Organization","site_admin":false},"repo":{"id":82211360,"name":"sonarwhal","full_name":"sonarwhal/sonarwhal","owner":{"login":"sonarwhal","id":25042266,"avatar_url":"https://avatars0.githubusercontent.com/u/25042266?v=4","gravatar_id":"","url":"https://api.github.com/users/sonarwhal","html_url":"https://github.com/sonarwhal","followers_url":"https://api.github.com/users/sonarwhal/followers","following_url":"https://api.github.com/users/sonarwhal/following{/other_user}","gists_url":"https://api.github.com/users/sonarwhal/gists{/gist_id}","starred_url":"https://api.github.com/users/sonarwhal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonarwhal/subscriptions","organizations_url":"https://api.github.com/users/sonarwhal/orgs","repos_url":"https://api.github.com/users/sonarwhal/repos","events_url":"https://api.github.com/users/sonarwhal/events{/privacy}","received_events_url":"https://api.github.com/users/sonarwhal/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sonarwhal/sonarwhal","description":"A linting tool for the web","fork":false,"url":"https://api.github.com/repos/sonarwhal/sonarwhal","forks_url":"https://api.github.com/repos/sonarwhal/sonarwhal/forks","keys_url":"https://api.github.com/repos/sonarwhal/sonarwhal/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sonarwhal/sonarwhal/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sonarwhal/sonarwhal/teams","hooks_url":"https://api.github.com/repos/sonarwhal/sonarwhal/hooks","issue_events_url":"https://api.github.com/repos/sonarwhal/sonarwhal/issues/events{/number}","events_url":"https://api.github.com/repos/sonarwhal/sonarwhal/events","assignees_url":"https://api.github.com/repos/sonarwhal/sonarwhal/assignees{/user}","branches_url":"https://api.github.com/repos/sonarwhal/sonarwhal/branches{/branch}","tags_url":"https://api.github.com/repos/sonarwhal/sonarwhal/tags","blobs_url":"https://api.github.com/repos/sonarwhal/sonarwhal/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sonarwhal/sonarwhal/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sonarwhal/sonarwhal/git/refs{/sha}","trees_url":"https://api.github.com/repos/sonarwhal/sonarwhal/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sonarwhal/sonarwhal/statuses/{sha}","languages_url":"https://api.github.com/repos/sonarwhal/sonarwhal/languages","stargazers_url":"https://api.github.com/repos/sonarwhal/sonarwhal/stargazers","contributors_url":"https://api.github.com/repos/sonarwhal/sonarwhal/contributors","subscribers_url":"https://api.github.com/repos/sonarwhal/sonarwhal/subscribers","subscription_url":"https://api.github.com/repos/sonarwhal/sonarwhal/subscription","commits_url":"https://api.github.com/repos/sonarwhal/sonarwhal/commits{/sha}","git_commits_url":"https://api.github.com/repos/sonarwhal/sonarwhal/git/commits{/sha}","comments_url":"https://api.github.com/repos/sonarwhal/sonarwhal/comments{/number}","issue_comment_url":"https://api.github.com/repos/sonarwhal/sonarwhal/issues/comments{/number}","contents_url":"https://api.github.com/repos/sonarwhal/sonarwhal/contents/{+path}","compare_url":"https://api.github.com/repos/sonarwhal/sonarwhal/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sonarwhal/sonarwhal/merges","archive_url":"https://api.github.com/repos/sonarwhal/sonarwhal/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sonarwhal/sonarwhal/downloads","issues_url":"https://api.github.com/repos/sonarwhal/sonarwhal/issues{/number}","pulls_url":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls{/number}","milestones_url":"https://api.github.com/repos/sonarwhal/sonarwhal/milestones{/number}","notifications_url":"https://api.github.com/repos/sonarwhal/sonarwhal/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sonarwhal/sonarwhal/labels{/name}","releases_url":"https://api.github.com/repos/sonarwhal/sonarwhal/releases{/id}","deployments_url":"https://api.github.com/repos/sonarwhal/sonarwhal/deployments","created_at":"2017-02-16T18:15:31Z","updated_at":"2018-03-13T02:43:58Z","pushed_at":"2018-03-13T04:01:05Z","git_url":"git://github.com/sonarwhal/sonarwhal.git","ssh_url":"git@github.com:sonarwhal/sonarwhal.git","clone_url":"https://github.com/sonarwhal/sonarwhal.git","svn_url":"https://github.com/sonarwhal/sonarwhal","homepage":"https://sonarwhal.com/","size":5874,"stargazers_count":1280,"watchers_count":1280,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":86,"mirror_url":null,"archived":false,"open_issues_count":86,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":86,"open_issues":86,"watchers":1280,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875"},"html":{"href":"https://github.com/sonarwhal/sonarwhal/pull/875"},"issue":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/issues/875"},"comments":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/issues/875/comments"},"review_comments":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875/comments"},"review_comment":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/pulls/875/commits"},"statuses":{"href":"https://api.github.com/repos/sonarwhal/sonarwhal/statuses/b6fb4af76fba5b120c7e7baed08a3a31477e8fe2"}},"author_association":"OWNER"}}
{"actor":{"display_login":"molant"}}