├── .github └── workflows │ └── build.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── SECURITY.md ├── SUPPORT.md ├── bin ├── console └── setup ├── fixtures └── vcr_cassettes │ ├── add_comment.yml │ ├── add_public_comment.yml │ ├── add_report_reference.yml │ ├── assign_report_to_group.yml │ ├── assign_report_to_group_no_permission.yml │ ├── assign_report_to_nobody.yml │ ├── assign_report_to_nobody_no_permission.yml │ ├── assign_report_to_user.yml │ ├── assign_report_to_user_no_permission.yml │ ├── award_a_bounty.yml │ ├── award_swag.yml │ ├── common_responses.yml │ ├── create_report.yml │ ├── create_report_invalid.yml │ ├── dup.yml │ ├── empty_report_list.yml │ ├── get_balance.yml │ ├── lock_report.yml │ ├── missing_report.yml │ ├── programs.yml │ ├── report.yml │ ├── report_list.yml │ ├── report_list_before.yml │ ├── report_list_triaged.yml │ ├── reporters.yml │ ├── server_error.yml │ ├── server_error_when_assigning_report_to_user.yml │ ├── stage_change.yml │ ├── suggest_a_bounty.yml │ ├── swag.yml │ ├── swag_sent.yml │ ├── traverse_through_3_activities.yml │ ├── traverse_through_all_activities.yml │ ├── triage_and_hook_assign_report_to_user.yml │ ├── update_policy.yml │ ├── update_severity.yml │ └── user_find_fransrosen.yml ├── hackerone-client.gemspec ├── lib └── hackerone │ ├── client.rb │ └── client │ ├── activity.rb │ ├── address.rb │ ├── asset.rb │ ├── attachment.rb │ ├── billing_balance.rb │ ├── bounty.rb │ ├── group.rb │ ├── incremental │ └── activities.rb │ ├── member.rb │ ├── organization.rb │ ├── program.rb │ ├── report.rb │ ├── reporter.rb │ ├── resource_helper.rb │ ├── structured_scope.rb │ ├── swag.rb │ ├── user.rb │ ├── version.rb │ └── weakness.rb └── spec ├── hackerone ├── client │ ├── activity_spec.rb │ ├── address_spec.rb │ ├── asset_spec.rb │ ├── attachment_spec.rb │ ├── program_spec.rb │ ├── report_spec.rb │ ├── reporter_spec.rb │ ├── swag_spec.rb │ ├── user_spec.rb │ └── weakness_spec.rb └── client_spec.rb └── spec_helper.rb /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build + Test 2 | on: [pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | build: 9 | name: Build + Test 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | ruby: [ '2.6', '2.7', '3.0', '3.1', '3.2', '3.3'] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Ruby ${{ matrix.ruby }} 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: ${{ matrix.ruby }} 20 | bundler-cache: true 21 | - name: Build and test with Rake with Ruby ${{ matrix.ruby }} 22 | run: bundle exec rake spec 23 | lint: 24 | name: Rubocop 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up Ruby 2.6 29 | uses: ruby/setup-ruby@v1 30 | with: 31 | ruby-version: 2.6 32 | bundler-cache: true 33 | - name: Run linters 34 | run: bundle exec rake rubocop 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | *.gem 14 | .ruby-version 15 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-github: 3 | - config/default.yml 4 | require: rubocop-performance 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.23.0] - 2024-08-09 2 | 3 | [Add Asset and Organization resources](https://github.com/github/hackerone-client/pull/10) (@jeffgran-dox) 4 | 5 | ## [0.22.0] - 2024-01-10 6 | 7 | [Update Faraday API call and deprecate support for Ruby <2.6](https://github.com/github/hackerone-client/pull/2) (@maclarel) 8 | 9 | ## [0.20.0] - 2020-10-21 10 | 11 | [Add ability to lock a report](https://github.com/oreoshake/hackerone-client/pull/59) (@rzhade3) 12 | [Add ability to list attachments on a report](https://github.com/oreoshake/hackerone-client/pull/58) (@rzhade3) 13 | 14 | ## [0.19.0] - 2020-05-08 15 | 16 | [Add ability to create a report](https://github.com/oreoshake/hackerone-client/pull/57) (@rzhade3) 17 | 18 | ## [0.18.0] - 2020-03-23 19 | 20 | [Add ability to filter reports on states other than "new"](https://github.com/oreoshake/hackerone-client/pull/54) (@rzhade3) 21 | 22 | ## [0.17.0] - 2020-03-23 23 | 24 | [Add support for updating the severity of an issue](https://github.com/oreoshake/hackerone-client/pull/50) (@rzhade3) 25 | 26 | ## [0.16.0] - 2020-03-23 27 | 28 | - Whooops, no change 29 | 30 | ## [0.15.0] - 2020-03-09 31 | 32 | - [Add 'update program policy' API support](https://github.com/oreoshake/hackerone-client/pull/47) (@rzhade3) 33 | 34 | ## [0.14.2] - 2020-03-09 35 | 36 | - [Don't lock development dependency versions](https://github.com/oreoshake/hackerone-client/pull/45) (@oreoshake) 37 | 38 | ## [0.14.1] - 2020-02-13 39 | 40 | - [Add support for float values in API responses](https://github.com/oreoshake/hackerone-client/pull/44) (@rzhade3) 41 | 42 | ## [0.14.0] - 2019-10-28 43 | 44 | - [Add new swag API support](https://github.com/oreoshake/hackerone-client/pull/41) (@anglinb) 45 | 46 | ## [0.13.0] - 2018-10-19 47 | 48 | - [Fix issue with `#to_owasp` which would raise an error if an issue classification started with `CAPEC-`](https://github.com/oreoshake/hackerone-client/pull/39) (@rzhade3) 49 | 50 | ## [0.12.0] - 2017-12-07 51 | 52 | - [Incremental activities](https://github.com/oreoshake/hackerone-client/pull/36) - iterate over actions in a program (@esjee) 53 | 54 | ## [0.11.0] - 2017-11-03 55 | 56 | - [Add state change hooks](https://github.com/oreoshake/hackerone-client/issues/25) (@esjee) 57 | 58 | ## [0.10.0] - 2017-11-03 59 | 60 | Oh no! I hate versions where one number is >= 10! We should figure out what's missing for a solid 1.0 release. 61 | 62 | - `HackerOne::Client::User.find(user_id)` returns an instance of `User` (@esjee) 63 | - Add "original report ID" field when marking things as duplicate. This invites the reporter to the original issue. 64 | 65 | ## [0.9.1] - 2017-10-24 66 | 67 | - Misc: loosen version restrictions on activesupport 68 | 69 | ## [0.9.0] - 2017-10-09 70 | 71 | - API: move actions from client into report (@esjee) 72 | 73 | This is a breaking change, but this is still not a 1.0 and shouldn't be considered stable. 74 | 75 | ## [0.8.0] - 2017-09-05 76 | 77 | - Feature: add ability to suggest and award swag, cash, and bonuses (@esjee) 78 | 79 | ## [0.7.0] - 2017-08-28 80 | 81 | - Feature: retrieve common responses (@esjee) 82 | 83 | ## [0.6.0] - 2017-07-24 84 | 85 | - Feature: comments (internal or not) can be added to reports 86 | 87 | ## [0.5.2] - 2017-07-19 88 | 89 | - Bugfix: structured scopes were not being populated correctly resulting in nil results for all attributes 90 | 91 | ## [0.5.1] - 2017-06-26 92 | 93 | - [Structure scope](https://api.hackerone.com/docs/v1#structured-scope) data added to report object 94 | 95 | ## [0.5.0] - 2017-06-23 96 | 97 | - `report.assign_to_user` and `report.assign_to_group` (@esjee) 98 | 99 | ## [0.4.0] - 2017-04-21 100 | 101 | - `client.reporters` to return all reporters for a given project (@esjee) 102 | - `HackerOne::Client::Program.find(program_name)` to return information about a given program (@esjee) 103 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This repository is maintained by: 2 | * @rzhade3 @maclarel 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/github/hackerone-client/fork 4 | [pr]: https://github.com/github/hackerone-client/compare 5 | [style]: https://github.com/styleguide/ruby 6 | [code-of-conduct]: CODE_OF_CONDUCT.md 7 | 8 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 9 | 10 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.txt). 11 | 12 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 13 | 14 | ## Submitting a pull request 15 | 16 | 0. [Fork][fork] and clone the repository 17 | 0. Configure and install the dependencies: `bundle install` 18 | 0. Make sure the tests pass on your machine: `bundle exec rspec spec` 19 | 0. Create a new branch: `git checkout -b my-branch-name` 20 | 0. Make your change, add tests, and make sure the tests still pass 21 | 0. Push to your fork and [submit a pull request][pr] 22 | 0. Pat your self on the back and wait for your pull request to be reviewed and merged. 23 | 24 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 25 | 26 | - Follow the [style guide][style]. 27 | - Write tests. 28 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 29 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 30 | 31 | ## Releasing 32 | 33 | 0. Ensure CI is green 34 | 0. Pull the latest code 35 | 0. Increment the VERSION constant 36 | 0. Run `gem build hackerone-client.gemspec` 37 | 0. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem 38 | 0. Test behavior locally, branch deploy, whatever needs to happen 39 | 0. Run `bundle exec rake release` 40 | 41 | ## Resources 42 | 43 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 44 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 45 | - [GitHub Help](https://help.github.com) 46 | 47 | 48 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in hackerone-client.gemspec 6 | gemspec 7 | 8 | group :developement do 9 | gem "pry" 10 | end 11 | 12 | group :test do 13 | gem "rubocop" 14 | gem "rubocop-github", ">= 0.16.0" 15 | gem "rubocop-performance" 16 | end 17 | 18 | group :guard do 19 | gem "growl", require: RUBY_PLATFORM.include?("darwin") && "growl" 20 | gem "guard-rspec" 21 | gem "rb-fsevent", require: RUBY_PLATFORM.include?("darwin") && "rb-fsevent" 22 | end 23 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | guard :rspec, cmd: "bundle exec rspec" do 4 | require "guard/rspec/dsl" 5 | dsl = Guard::RSpec::Dsl.new(self) 6 | 7 | # Feel free to open issues for suggestions and improvements 8 | 9 | # RSpec files 10 | rspec = dsl.rspec 11 | watch(rspec.spec_helper) { rspec.spec_dir } 12 | watch(rspec.spec_support) { rspec.spec_dir } 13 | watch(rspec.spec_files) 14 | 15 | # Ruby files 16 | ruby = dsl.ruby 17 | dsl.watch_spec_files_for(ruby.lib_files) 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 GitHub, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hackerone::Client 2 | 3 | A limited client library for interacting with HackerOne in Ruby. Currently supports a few operations: 4 | 5 | ```ruby 6 | client = HackerOne::Client::Api.new("github") 7 | 8 | # POST '/reports' creates a new report 9 | client.create_report(title: "hi", summary: "hi", impact: "string", severity_rating: :high, source: "api") 10 | 11 | # GET '/reports' returns all reports in a given state for a program, by default :new 12 | client.reports(since: 10.days.ago, before: 1.day.ago, state: :new) 13 | 14 | # GET '/report/{id}' returns report data for a given report 15 | report = client.report(id) 16 | 17 | # PUT '/reports/{id}/assignee' 18 | report.assign_to_user("username") 19 | report.assign_to_group("groupname") 20 | 21 | # POST '/reports/#{id}/activities' 22 | report.add_comment(message, internal: false) # internal is true by default 23 | 24 | # POST '/report/{id}/state_change change the state of a report 25 | # `state` can be one of new, triaged, needs-more-info, resolved, not-applicable, informative, duplicate, spam 26 | # when marking as duplicate, you can supply the original report ID 27 | report.state_change(:duplicate, "Your issue has been marked as X", original_report_id: 12345) 28 | 29 | # POST '/report/{id}/add_report_reference add a "reference" e.g. internal issue number 30 | report.add_report_reference(reference) 31 | 32 | # Triage an issue (add a reference and set state to :triaged) 33 | report.triage(reference) 34 | 35 | # Set the severity on a report (rating can be :none, :low, :medium, :high or :critical) 36 | report.update_severity(rating: :high) 37 | 38 | # POST /reports/{id}/bounty_suggestions 39 | report.suggest_bounty(message: "I suggest $500 with a small bonus. Report is well-written.", amount: 500, bonus_amount: 50) 40 | 41 | # POST /reports/{id}/bounties 42 | report.award_bounty(message: "Here's your bounty!", amount: 500, bonus_amount: 50) 43 | 44 | # POST /reports/{id}/swags 45 | report.award_swag(message: "Here's your T-Shirt") 46 | 47 | # GET `/{program}/reporters` returns a list of unique reporters that have reported to your program 48 | client.reporters 49 | 50 | program = HackerOne::Client::Program.find("insert-program-name-here") 51 | 52 | # returns all common responses 53 | program.common_responses 54 | 55 | # Updates a program's policy 56 | program.update_policy(policy: "Please submit valid vulnerabilities") 57 | 58 | # Gets a program's balance 59 | program.balance 60 | 61 | # Gets a list of structured scopes 62 | program.structured_scopes 63 | 64 | # Gets an organization for a program 65 | program.organization 66 | 67 | # Gets assets for an organization 68 | program.organization.assets 69 | 70 | # Updates an asset for an organization 71 | asset = program.organization.assets[0] 72 | asset.update( 73 | attributes: { 74 | description: "This is the new description" 75 | } 76 | ) 77 | ``` 78 | 79 | ## State change hooks 80 | 81 | You can add hooks that will be called for every state change. This can be useful e.g. for ensuring that reports always get assigned or calling out to external services for specific state changes. 82 | 83 | ```ruby 84 | # Initialization 85 | 86 | HackerOne::Client::Report.add_state_change_hook ->(report, old_state, new_state) do 87 | # ... 88 | end 89 | ``` 90 | 91 | ## Usage 92 | 93 | ### Credential management 94 | 95 | You'll need to generate an API token at `https://hackerone.com//api`. 96 | 97 | * Click "Create API token" 98 | * Name the token 99 | * Click "Create" 100 | * Copy down the value 101 | 102 | **Set the `HACKERONE_TOKEN` and `HACKERONE_TOKEN_NAME` environment variables.** 103 | 104 | ### Program name 105 | 106 | In order to retrieve all reports for a given program, you need to supply a default program: 107 | 108 | ```ruby 109 | HackerOne::Client.program = "github" 110 | ``` 111 | 112 | ### Risk classification 113 | 114 | Configure the low/med/high/crit ranges for easier classification based on payouts: 115 | 116 | ```ruby 117 | HackerOne::Client.low_range = 1..999 118 | HackerOne::Client.medium_range = 1000...2500 119 | HackerOne::Client.high_range = 2500...5000 120 | HackerOne::Client.critical_range = 5000...100_000_000 121 | ``` 122 | 123 | ### Configuration 124 | 125 | In order to configure whether error handling is strict or lenient, set the `HACKERONE_CLIENT_LENIENT_MODE` variable. 126 | 127 | Setting this variable will make the client try to absorb errors, like a malformed bounty or bonus amount. Not setting this variable will cause the client to raise errors. 128 | 129 | ## Contributing 130 | 131 | Bug reports and pull requests are welcome on GitHub at https://github.com/oreoshake/hackerone-client. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 132 | 133 | 134 | ## License 135 | 136 | This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE.txt) for the full terms. 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | 10 | begin 11 | require "rubocop/rake_task" 12 | RuboCop::RakeTask.new 13 | rescue LoadError 14 | task(:rubocop) { $stderr.puts "RuboCop is disabled" } 15 | end 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | # Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/github/site-policy/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) 32 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. 6 | 7 | For help or questions about using this project, please file an issue on this repository. 8 | 9 | - `hackerone-client` is not actively developed but is maintained by GitHub staff **AND THE COMMUNITY**. We will do our best to respond to support and community questions in a timely manner. 10 | 11 | ## GitHub Support Policy 12 | 13 | Support for this project is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "hackerone/client" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/add_comment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/activities 6 | body: 7 | encoding: UTF-8 8 | string: "{\"data\":{\"type\":\"activity-comment\",\"attributes\":{\"message\":\"I 9 | am an internal comment\",\"internal\":true}}}" 10 | headers: 11 | Authorization: 12 | - Basic == 13 | User-Agent: 14 | - Faraday v0.11.0 15 | Content-Type: 16 | - application/json 17 | Accept-Encoding: 18 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 19 | Accept: 20 | - "*/*" 21 | response: 22 | status: 23 | code: 201 24 | message: Created 25 | headers: 26 | Date: 27 | - Thu, 20 Jul 2017 19:31:19 GMT 28 | Content-Type: 29 | - application/json; charset=utf-8 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - __cfduid=dc6e0045651a0dfb269f81402a0ee74051500579079; expires=Fri, 20-Jul-18 36 | 19:31:19 GMT; path=/; Domain=api.hackerone.com; HttpOnly 37 | X-Request-Id: 38 | - 552f630f-8f34-4e39-b49f-f4cf6046dd1b 39 | Etag: 40 | - W/"213a426a179715f6d44f095780be6b17" 41 | Cache-Control: 42 | - max-age=0, private, must-revalidate 43 | Strict-Transport-Security: 44 | - max-age=31536000; includeSubDomains; preload 45 | Content-Security-Policy: 46 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 47 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 48 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 49 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 50 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 51 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 52 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 53 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 54 | X-Content-Type-Options: 55 | - nosniff 56 | X-Download-Options: 57 | - noopen 58 | X-Frame-Options: 59 | - DENY 60 | X-Permitted-Cross-Domain-Policies: 61 | - none 62 | X-Xss-Protection: 63 | - 1; mode=block 64 | Public-Key-Pins-Report-Only: 65 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 66 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 67 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 68 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 69 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 70 | Server: 71 | - cloudflare-nginx 72 | Cf-Ray: 73 | - 3818570f7dce53a2-LAX 74 | body: 75 | encoding: UTF-8 76 | string: "{\"data\":{\"type\":\"activity-comment\",\"id\":\"1854710\",\"attributes\":{\"message\":\"I 77 | am an internal comment\",\"created_at\":\"2017-07-20T19:31:19.733Z\",\"updated_at\":\"2017-07-20T19:31:19.733Z\",\"internal\":true},\"relationships\":{\"actor\":{\"data\":{\"type\":\"user\",\"id\":\"185283\",\"attributes\":{\"username\":\"oreoshake-test-token-4\",\"name\":null,\"disabled\":false,\"created_at\":\"2017-07-20T19:22:56.881Z\",\"profile_picture\":{\"62x62\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\",\"82x82\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\",\"110x110\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\",\"260x260\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\"}}}}}}}" 78 | http_version: 79 | recorded_at: Thu, 20 Jul 2017 19:31:19 GMT 80 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/add_public_comment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/activities 6 | body: 7 | encoding: UTF-8 8 | string: "{\"data\":{\"type\":\"activity-comment\",\"attributes\":{\"message\":\"I 9 | am not an internal comment\",\"internal\":false}}}" 10 | headers: 11 | Authorization: 12 | - Basic == 13 | User-Agent: 14 | - Faraday v0.11.0 15 | Content-Type: 16 | - application/json 17 | Accept-Encoding: 18 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 19 | Accept: 20 | - "*/*" 21 | response: 22 | status: 23 | code: 201 24 | message: Created 25 | headers: 26 | Date: 27 | - Thu, 20 Jul 2017 19:31:20 GMT 28 | Content-Type: 29 | - application/json; charset=utf-8 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - __cfduid=d104d11a7dd0d4d546ad5de4a34ae70091500579080; expires=Fri, 20-Jul-18 36 | 19:31:20 GMT; path=/; Domain=api.hackerone.com; HttpOnly 37 | X-Request-Id: 38 | - d3253b5d-6f40-4070-8a49-2c9fddc85b6f 39 | Etag: 40 | - W/"5409aa55cb4b50a7801681b8f529bcfd" 41 | Cache-Control: 42 | - max-age=0, private, must-revalidate 43 | Strict-Transport-Security: 44 | - max-age=31536000; includeSubDomains; preload 45 | Content-Security-Policy: 46 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 47 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 48 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 49 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 50 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 51 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 52 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 53 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 54 | X-Content-Type-Options: 55 | - nosniff 56 | X-Download-Options: 57 | - noopen 58 | X-Frame-Options: 59 | - DENY 60 | X-Permitted-Cross-Domain-Policies: 61 | - none 62 | X-Xss-Protection: 63 | - 1; mode=block 64 | Public-Key-Pins-Report-Only: 65 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 66 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 67 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 68 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 69 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 70 | Server: 71 | - cloudflare-nginx 72 | Cf-Ray: 73 | - 381857128fff7820-LAX 74 | body: 75 | encoding: UTF-8 76 | string: "{\"data\":{\"type\":\"activity-comment\",\"id\":\"1854711\",\"attributes\":{\"message\":\"I 77 | am not an internal comment\",\"created_at\":\"2017-07-20T19:31:20.181Z\",\"updated_at\":\"2017-07-20T19:31:20.181Z\",\"internal\":false},\"relationships\":{\"actor\":{\"data\":{\"type\":\"user\",\"id\":\"185283\",\"attributes\":{\"username\":\"oreoshake-test-token-4\",\"name\":null,\"disabled\":false,\"created_at\":\"2017-07-20T19:22:56.881Z\",\"profile_picture\":{\"62x62\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\",\"82x82\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\",\"110x110\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\",\"260x260\":\"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png\"}}}}}}}" 78 | http_version: 79 | recorded_at: Thu, 20 Jul 2017 19:31:20 GMT 80 | recorded_with: VCR 3.0.3 81 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/add_report_reference.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/issue_tracker_reference_id 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"issue-tracker-reference-id","attributes":{"reference":"fooooo"}}}' 9 | headers: 10 | Authorization: 11 | - Basic nope 12 | User-Agent: 13 | - Faraday v0.11.0 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Thu, 16 Mar 2017 20:21:44 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=d4b57ca8890f7f0e1428003db73c2a8f41489695704; expires=Fri, 16-Mar-18 35 | 20:21:44 GMT; path=/; Domain=api.hackerone.com; HttpOnly 36 | X-Request-Id: 37 | - 2c20eb84-1d41-48c2-a64a-6fffd601dce9 38 | Etag: 39 | - W/"e6e4cf756fbd434e3375eec1c1256611" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | Content-Security-Policy: 45 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 46 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 47 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 48 | X-Content-Type-Options: 49 | - nosniff 50 | X-Download-Options: 51 | - noopen 52 | X-Frame-Options: 53 | - DENY 54 | X-Permitted-Cross-Domain-Policies: 55 | - none 56 | X-Xss-Protection: 57 | - 1; mode=block 58 | Public-Key-Pins-Report-Only: 59 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 60 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 61 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 62 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 63 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 64 | Server: 65 | - cloudflare-nginx 66 | Cf-Ray: 67 | - 340a6ba9fa5653a8-LAX 68 | body: 69 | encoding: ASCII-8BIT 70 | string: '{"relationships":{"report":{"data":{"id":"200","type":"report","attributes":{"title":"ssss","state":"triaged","created_at":"2016-04-18T22:24:50.065Z","vulnerability_information":"sssss","triaged_at":"2017-03-16T19:53:49.939Z","closed_at":null,"last_reporter_activity_at":"2016-04-18T22:24:50.118Z","first_program_activity_at":"2017-03-16T18:36:40.650Z","last_program_activity_at":"2017-03-16T19:53:49.939Z","bounty_awarded_at":null,"swag_awarded_at":null,"disclosed_at":null,"last_activity_at":"2017-03-16T19:53:49.939Z","issue_tracker_reference_id":"fooooo"},"relationships":{"reporter":{"data":{"id":"57690","type":"user","attributes":{"username":"ndm-github","name":"Neil 71 | Matatall","disabled":false,"created_at":"2016-02-24T01:33:01.258Z","profile_picture":{"62x62":"https://profile-photos.hackerone-user-content.com/production/000/057/690/1e0c9ef6fc8bcc17806ae82e6f73cdd4d0e74eb9_small.jpg?1469554487","82x82":"https://profile-photos.hackerone-user-content.com/production/000/057/690/f6a17c40a6c910ba801014d1498b55727ea858e3_medium.jpg?1469554487","110x110":"https://profile-photos.hackerone-user-content.com/production/000/057/690/2259dde15230756d99f68a9ca824af11081ab965_large.jpg?1469554487","260x260":"https://profile-photos.hackerone-user-content.com/production/000/057/690/6d2da33805fef8b8ac4cf513e1562699e79365e0_xtralarge.jpg?1469554487"}}}},"program":{"data":{"id":"11767","type":"program","attributes":{"handle":"github-test","created_at":"2016-04-15T17:10:31.261Z","updated_at":"2016-09-20T14:54:15.448Z"}}},"swag":{"data":[]},"attachments":{"data":[]},"vulnerability_types":{"data":[{"id":"107921","type":"vulnerability-type","attributes":{"name":"Cross-Site 72 | Scripting (XSS)","description":"Failure of a site to validate, filter, or 73 | encode user input before returning it to another user''s web client.\n","created_at":"2016-04-15T17:10:39.169Z"}}]},"activities":{"data":[{"type":"activity-reference-id-added","id":"1546419","attributes":{"message":"","created_at":"2017-03-16T20:21:44.883Z","updated_at":"2017-03-16T20:21:44.883Z","internal":true,"reference":"fooooo","reference_url":null},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"type":"activity-bug-triaged","id":"1546376","attributes":{"message":"This 74 | is has been triaged internally.","created_at":"2017-03-16T19:53:49.939Z","updated_at":"2017-03-16T19:53:49.939Z","internal":false},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"type":"activity-reference-id-added","id":"1546123","attributes":{"message":"","created_at":"2017-03-16T18:36:49.045Z","updated_at":"2017-03-16T18:36:49.045Z","internal":true,"reference":"3476","reference_url":null},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"type":"activity-bug-needs-more-info","id":"1546120","attributes":{"message":"This 75 | is has been triaged internally.","created_at":"2017-03-16T18:36:40.650Z","updated_at":"2017-03-16T18:36:40.650Z","internal":false},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}}]},"bounties":{"data":[]},"summaries":{"data":[]}}}}}}' 76 | http_version: 77 | recorded_at: Thu, 16 Mar 2017 20:21:45 GMT 78 | recorded_with: VCR 3.0.3 79 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/assign_report_to_group_no_permission.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/me/programs 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic nope= 12 | User-Agent: 13 | - Faraday v0.12.1 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Tue, 23 May 2017 20:41:22 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=d1b5421708c94400c8729f6c3c2d9e0561495572081; expires=Wed, 23-May-18 33 | 20:41:21 GMT; path=/; Domain=api.hackerone.com; HttpOnly 34 | X-Request-Id: 35 | - 3933d272-4ceb-4272-b101-4652f141d098 36 | Etag: 37 | - W/"9329ef6b8f370e76d96d71977a6480c4" 38 | Cache-Control: 39 | - max-age=0, private, must-revalidate 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 44 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 45 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 46 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 47 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 48 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 49 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 50 | X-Content-Type-Options: 51 | - nosniff 52 | X-Download-Options: 53 | - noopen 54 | X-Frame-Options: 55 | - DENY 56 | X-Permitted-Cross-Domain-Policies: 57 | - none 58 | X-Xss-Protection: 59 | - 1; mode=block 60 | Public-Key-Pins-Report-Only: 61 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 62 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 63 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 64 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 65 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 66 | Server: 67 | - cloudflare-nginx 68 | Cf-Ray: 69 | - 363ad5e468200c1d-AMS 70 | body: 71 | encoding: ASCII-8BIT 72 | string: '{"data":[{"id":"18969","type":"program","attributes":{"handle":"github","created_at":"2017-03-08T19:22:41.884Z","updated_at":"2017-04-09T08:15:15.228Z"}}],"links":{}}' 73 | http_version: 74 | recorded_at: Tue, 23 May 2017 20:40:42 GMT 75 | - request: 76 | method: get 77 | uri: https://api.hackerone.com/v1/programs/18969 78 | body: 79 | encoding: US-ASCII 80 | string: '' 81 | headers: 82 | Authorization: 83 | - Basic nope= 84 | User-Agent: 85 | - Faraday v0.12.1 86 | Accept-Encoding: 87 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 88 | Accept: 89 | - "*/*" 90 | response: 91 | status: 92 | code: 200 93 | message: OK 94 | headers: 95 | Date: 96 | - Tue, 23 May 2017 20:41:22 GMT 97 | Content-Type: 98 | - application/json; charset=utf-8 99 | Transfer-Encoding: 100 | - chunked 101 | Connection: 102 | - keep-alive 103 | Set-Cookie: 104 | - __cfduid=dfae2421a96e1240f9861bc1d666d38271495572082; expires=Wed, 23-May-18 105 | 20:41:22 GMT; path=/; Domain=api.hackerone.com; HttpOnly 106 | X-Request-Id: 107 | - ace6d9f4-f6b2-4a8a-aa21-221b7816f4d0 108 | Etag: 109 | - W/"f4712fdd08f66d872753172d327d1327" 110 | Cache-Control: 111 | - max-age=0, private, must-revalidate 112 | Strict-Transport-Security: 113 | - max-age=31536000; includeSubDomains; preload 114 | Content-Security-Policy: 115 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 116 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 117 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 118 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 119 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 120 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 121 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 122 | X-Content-Type-Options: 123 | - nosniff 124 | X-Download-Options: 125 | - noopen 126 | X-Frame-Options: 127 | - DENY 128 | X-Permitted-Cross-Domain-Policies: 129 | - none 130 | X-Xss-Protection: 131 | - 1; mode=block 132 | Public-Key-Pins-Report-Only: 133 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 134 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 135 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 136 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 137 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 138 | Server: 139 | - cloudflare-nginx 140 | Cf-Ray: 141 | - 363ad5e99c5b72e9-AMS 142 | body: 143 | encoding: ASCII-8BIT 144 | string: '{"data":{"id":"18969","type":"program","attributes":{"handle":"github","created_at":"2017-03-08T19:22:41.884Z","updated_at":"2017-04-09T08:15:15.228Z"},"relationships":{"groups":{"data":[{"id":"23579","type":"group","attributes":{"name":"Standard","created_at":"2017-03-08T19:22:43.259Z","permissions":["report_management","reward_management"]}},{"id":"23578","type":"group","attributes":{"name":"Admin","created_at":"2017-03-08T19:22:43.243Z","permissions":["user_management","program_management"]}}]},"members":{"data":[{"id":"39241","type":"member","attributes":{"created_at":"2017-05-23T20:19:30.830Z","permissions":[]},"relationships":{"user":{"data":{"id":"170310","type":"user","attributes":{"username":"esjee","name":null,"disabled":false,"created_at":"2017-05-23T20:19:30.805Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"id":"32557","type":"member","attributes":{"created_at":"2017-03-08T19:22:44.421Z","permissions":["program_management","report_management","reward_management","user_management"]},"relationships":{"user":{"data":{"id":"147515","type":"user","attributes":{"username":"esjee","name":"esjee","disabled":false,"created_at":"2017-03-04T16:31:33.595Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"id":"32556","type":"member","attributes":{"created_at":"2017-03-08T19:22:43.226Z","permissions":["program_management","report_management","reward_management","user_management"]},"relationships":{"user":{"data":{"id":"4954","type":"user","attributes":{"username":"demo-member","name":"Demo 145 | Member","disabled":false,"created_at":"2014-04-14T11:45:00.949Z","profile_picture":{"62x62":"https://profile-photos.hackerone-user-content.com/production/000/004/954/76e628d12eaacde79878b890df02c065f740b1a4_small.png?1423472456","82x82":"https://profile-photos.hackerone-user-content.com/production/000/004/954/75e4cf2599f591e618646429db57d986e496ccee_medium.png?1423472456","110x110":"https://profile-photos.hackerone-user-content.com/production/000/004/954/25f5c8af70323cacb2c2ffa17d68cac2500dc410_large.png?1423472456","260x260":"https://profile-photos.hackerone-user-content.com/production/000/004/954/d903c042cdc7798ad76684563624ee7ea071aed8_xtralarge.png?1423472456"}}}}}}]}}}}' 146 | http_version: 147 | recorded_at: Tue, 23 May 2017 20:40:43 GMT 148 | - request: 149 | method: put 150 | uri: https://api.hackerone.com/v1/reports/200/assignee 151 | body: 152 | encoding: UTF-8 153 | string: '{"data":{"type":"group","id":"23578"}}' 154 | headers: 155 | Authorization: 156 | - Basic nope= 157 | User-Agent: 158 | - Faraday v0.12.1 159 | Content-Type: 160 | - application/json 161 | Accept-Encoding: 162 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 163 | Accept: 164 | - "*/*" 165 | response: 166 | status: 167 | code: 403 168 | message: Forbidden 169 | headers: 170 | Date: 171 | - Tue, 23 May 2017 20:41:23 GMT 172 | Content-Type: 173 | - application/json; charset=utf-8 174 | Transfer-Encoding: 175 | - chunked 176 | Connection: 177 | - keep-alive 178 | Set-Cookie: 179 | - __cfduid=dcc678c2a1f78a938b08c6a45ebbb98401495572083; expires=Wed, 23-May-18 180 | 20:41:23 GMT; path=/; Domain=api.hackerone.com; HttpOnly 181 | X-Request-Id: 182 | - c53322a5-51b9-4c70-a080-c1e079f7ebf8 183 | Cache-Control: 184 | - no-cache 185 | Strict-Transport-Security: 186 | - max-age=31536000; includeSubDomains; preload 187 | Content-Security-Policy: 188 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 189 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 190 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 191 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 192 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 193 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 194 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 195 | X-Content-Type-Options: 196 | - nosniff 197 | X-Download-Options: 198 | - noopen 199 | X-Frame-Options: 200 | - DENY 201 | X-Permitted-Cross-Domain-Policies: 202 | - none 203 | X-Xss-Protection: 204 | - 1; mode=block 205 | Public-Key-Pins-Report-Only: 206 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 207 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 208 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 209 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 210 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 211 | Server: 212 | - cloudflare-nginx 213 | Cf-Ray: 214 | - 363ad5eef9df72a7-AMS 215 | body: 216 | encoding: ASCII-8BIT 217 | string: '{"errors":[{"status":403}]}' 218 | http_version: 219 | recorded_at: Tue, 23 May 2017 20:40:43 GMT 220 | recorded_with: VCR 3.0.3 221 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/assign_report_to_nobody_no_permission.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: put 5 | uri: https://api.hackerone.com/v1/reports/200/assignee 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"nobody"}}' 9 | headers: 10 | Authorization: 11 | - Basic nope= 12 | User-Agent: 13 | - Faraday v0.12.1 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 403 23 | message: Forbidden 24 | headers: 25 | Date: 26 | - Tue, 23 May 2017 20:41:46 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=d8861410574354af8a263a2ebe8e96a8c1495572106; expires=Wed, 23-May-18 35 | 20:41:46 GMT; path=/; Domain=api.hackerone.com; HttpOnly 36 | X-Request-Id: 37 | - 8ad6c63f-b787-44cc-bffc-eab353a5b56e 38 | Cache-Control: 39 | - no-cache 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 44 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 45 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 46 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 47 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 48 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 49 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 50 | X-Content-Type-Options: 51 | - nosniff 52 | X-Download-Options: 53 | - noopen 54 | X-Frame-Options: 55 | - DENY 56 | X-Permitted-Cross-Domain-Policies: 57 | - none 58 | X-Xss-Protection: 59 | - 1; mode=block 60 | Public-Key-Pins-Report-Only: 61 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 62 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 63 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 64 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 65 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 66 | Server: 67 | - cloudflare-nginx 68 | Cf-Ray: 69 | - 363ad67eabde7325-AMS 70 | body: 71 | encoding: ASCII-8BIT 72 | string: '{"errors":[{"status":403}]}' 73 | http_version: 74 | recorded_at: Tue, 23 May 2017 20:41:07 GMT 75 | recorded_with: VCR 3.0.3 76 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/assign_report_to_user_no_permission.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/me/programs 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic nope= 12 | User-Agent: 13 | - Faraday v0.12.1 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Tue, 23 May 2017 20:40:57 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=d0698c5c1728c3e911ca6edb0fabad1db1495572057; expires=Wed, 23-May-18 33 | 20:40:57 GMT; path=/; Domain=api.hackerone.com; HttpOnly 34 | X-Request-Id: 35 | - f13a33b5-e863-484d-ae01-b988b82dce02 36 | Etag: 37 | - W/"9329ef6b8f370e76d96d71977a6480c4" 38 | Cache-Control: 39 | - max-age=0, private, must-revalidate 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 44 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 45 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 46 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 47 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 48 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 49 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 50 | X-Content-Type-Options: 51 | - nosniff 52 | X-Download-Options: 53 | - noopen 54 | X-Frame-Options: 55 | - DENY 56 | X-Permitted-Cross-Domain-Policies: 57 | - none 58 | X-Xss-Protection: 59 | - 1; mode=block 60 | Public-Key-Pins-Report-Only: 61 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 62 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 63 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 64 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 65 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 66 | Server: 67 | - cloudflare-nginx 68 | Cf-Ray: 69 | - 363ad54f7bad2c3c-AMS 70 | body: 71 | encoding: ASCII-8BIT 72 | string: '{"data":[{"id":"18969","type":"program","attributes":{"handle":"github","created_at":"2017-03-08T19:22:41.884Z","updated_at":"2017-04-09T08:15:15.228Z"}}],"links":{}}' 73 | http_version: 74 | recorded_at: Tue, 23 May 2017 20:40:18 GMT 75 | - request: 76 | method: get 77 | uri: https://api.hackerone.com/v1/programs/18969 78 | body: 79 | encoding: US-ASCII 80 | string: '' 81 | headers: 82 | Authorization: 83 | - Basic nope= 84 | User-Agent: 85 | - Faraday v0.12.1 86 | Accept-Encoding: 87 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 88 | Accept: 89 | - "*/*" 90 | response: 91 | status: 92 | code: 200 93 | message: OK 94 | headers: 95 | Date: 96 | - Tue, 23 May 2017 20:40:58 GMT 97 | Content-Type: 98 | - application/json; charset=utf-8 99 | Transfer-Encoding: 100 | - chunked 101 | Connection: 102 | - keep-alive 103 | Set-Cookie: 104 | - __cfduid=de4c9eb42db4668a5a6e39d28a23abf281495572057; expires=Wed, 23-May-18 105 | 20:40:57 GMT; path=/; Domain=api.hackerone.com; HttpOnly 106 | X-Request-Id: 107 | - f2c05a8d-3c40-46fb-814e-25bd413a4154 108 | Etag: 109 | - W/"f4712fdd08f66d872753172d327d1327" 110 | Cache-Control: 111 | - max-age=0, private, must-revalidate 112 | Strict-Transport-Security: 113 | - max-age=31536000; includeSubDomains; preload 114 | Content-Security-Policy: 115 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 116 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 117 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 118 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 119 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 120 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 121 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 122 | X-Content-Type-Options: 123 | - nosniff 124 | X-Download-Options: 125 | - noopen 126 | X-Frame-Options: 127 | - DENY 128 | X-Permitted-Cross-Domain-Policies: 129 | - none 130 | X-Xss-Protection: 131 | - 1; mode=block 132 | Public-Key-Pins-Report-Only: 133 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 134 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 135 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 136 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 137 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 138 | Server: 139 | - cloudflare-nginx 140 | Cf-Ray: 141 | - 363ad551d84472a7-AMS 142 | body: 143 | encoding: ASCII-8BIT 144 | string: '{"data":{"id":"18969","type":"program","attributes":{"handle":"github","created_at":"2017-03-08T19:22:41.884Z","updated_at":"2017-04-09T08:15:15.228Z"},"relationships":{"groups":{"data":[{"id":"23579","type":"group","attributes":{"name":"Standard","created_at":"2017-03-08T19:22:43.259Z","permissions":["report_management","reward_management"]}},{"id":"23578","type":"group","attributes":{"name":"Admin","created_at":"2017-03-08T19:22:43.243Z","permissions":["user_management","program_management"]}}]},"members":{"data":[{"id":"39241","type":"member","attributes":{"created_at":"2017-05-23T20:19:30.830Z","permissions":[]},"relationships":{"user":{"data":{"id":"170310","type":"user","attributes":{"username":"esjee","name":null,"disabled":false,"created_at":"2017-05-23T20:19:30.805Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"id":"32557","type":"member","attributes":{"created_at":"2017-03-08T19:22:44.421Z","permissions":["program_management","report_management","reward_management","user_management"]},"relationships":{"user":{"data":{"id":"147515","type":"user","attributes":{"username":"esjee","name":"esjee","disabled":false,"created_at":"2017-03-04T16:31:33.595Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"id":"32556","type":"member","attributes":{"created_at":"2017-03-08T19:22:43.226Z","permissions":["program_management","report_management","reward_management","user_management"]},"relationships":{"user":{"data":{"id":"4954","type":"user","attributes":{"username":"demo-member","name":"Demo 145 | Member","disabled":false,"created_at":"2014-04-14T11:45:00.949Z","profile_picture":{"62x62":"https://profile-photos.hackerone-user-content.com/production/000/004/954/76e628d12eaacde79878b890df02c065f740b1a4_small.png?1423472456","82x82":"https://profile-photos.hackerone-user-content.com/production/000/004/954/75e4cf2599f591e618646429db57d986e496ccee_medium.png?1423472456","110x110":"https://profile-photos.hackerone-user-content.com/production/000/004/954/25f5c8af70323cacb2c2ffa17d68cac2500dc410_large.png?1423472456","260x260":"https://profile-photos.hackerone-user-content.com/production/000/004/954/d903c042cdc7798ad76684563624ee7ea071aed8_xtralarge.png?1423472456"}}}}}}]}}}}' 146 | http_version: 147 | recorded_at: Tue, 23 May 2017 20:40:19 GMT 148 | - request: 149 | method: put 150 | uri: https://api.hackerone.com/v1/reports/200/assignee 151 | body: 152 | encoding: UTF-8 153 | string: '{"data":{"type":"user","id":"147515"}}' 154 | headers: 155 | Authorization: 156 | - Basic nope= 157 | User-Agent: 158 | - Faraday v0.12.1 159 | Content-Type: 160 | - application/json 161 | Accept-Encoding: 162 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 163 | Accept: 164 | - "*/*" 165 | response: 166 | status: 167 | code: 403 168 | message: Forbidden 169 | headers: 170 | Date: 171 | - Tue, 23 May 2017 20:40:59 GMT 172 | Content-Type: 173 | - application/json; charset=utf-8 174 | Transfer-Encoding: 175 | - chunked 176 | Connection: 177 | - keep-alive 178 | Set-Cookie: 179 | - __cfduid=d25bc900107d8c760caf4ebf661eb56e91495572058; expires=Wed, 23-May-18 180 | 20:40:58 GMT; path=/; Domain=api.hackerone.com; HttpOnly 181 | X-Request-Id: 182 | - e54dde64-2b87-48ab-8878-5f81e579d7c0 183 | Cache-Control: 184 | - no-cache 185 | Strict-Transport-Security: 186 | - max-age=31536000; includeSubDomains; preload 187 | Content-Security-Policy: 188 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 189 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 190 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 191 | ''self'' data: cover-photos.hackerone-user-content.com profile-photos.hackerone-user-content.com 192 | hackerone-attachments.s3.amazonaws.com; media-src ''self'' hackerone-attachments.s3.amazonaws.com; 193 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 194 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 195 | X-Content-Type-Options: 196 | - nosniff 197 | X-Download-Options: 198 | - noopen 199 | X-Frame-Options: 200 | - DENY 201 | X-Permitted-Cross-Domain-Policies: 202 | - none 203 | X-Xss-Protection: 204 | - 1; mode=block 205 | Public-Key-Pins-Report-Only: 206 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 207 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 208 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 209 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 210 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 211 | Server: 212 | - cloudflare-nginx 213 | Cf-Ray: 214 | - 363ad556fca372e9-AMS 215 | body: 216 | encoding: ASCII-8BIT 217 | string: '{"errors":[{"status":403}]}' 218 | http_version: 219 | recorded_at: Tue, 23 May 2017 20:40:20 GMT 220 | recorded_with: VCR 3.0.3 221 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/award_a_bounty.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/bounties 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"message":"Thanks for the great report!","amount":1330,"bonus_amount":7}}' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v0.13.0 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 201 23 | message: Created 24 | headers: 25 | Date: 26 | - Tue, 22 Aug 2017 15:03:46 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=d068dbf4c0fe50bf2d44f3cb68388bbd11503414225; expires=Wed, 22-Aug-18 35 | 15:03:45 GMT; path=/; Domain=api.hackerone.com; HttpOnly 36 | X-Request-Id: 37 | - 723974f5-3988-4f59-ae9e-70198ab702d9 38 | Etag: 39 | - W/"f8d7a0dd4f35f9a89533b12bc651ccca" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | Content-Security-Policy: 45 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 46 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 47 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 48 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 49 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 50 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 51 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 52 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 53 | X-Content-Type-Options: 54 | - nosniff 55 | X-Download-Options: 56 | - noopen 57 | X-Frame-Options: 58 | - DENY 59 | X-Permitted-Cross-Domain-Policies: 60 | - none 61 | X-Xss-Protection: 62 | - 1; mode=block 63 | Public-Key-Pins-Report-Only: 64 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 65 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 66 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 67 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 68 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 69 | Server: 70 | - cloudflare-nginx 71 | Cf-Ray: 72 | - 3926b87dce5c0761-AMS 73 | body: 74 | encoding: UTF-8 75 | string: '{"data":{"id":"58549","type":"bounty","attributes":{"amount":"1330.00","bonus_amount":"7.00","awarded_amount":"1330.00","awarded_bonus_amount":"7.00","awarded_currency":"USD","created_at":"2017-08-22T15:03:46.183Z"}}}' 76 | http_version: 77 | recorded_at: Tue, 22 Aug 2017 15:03:45 GMT 78 | recorded_with: VCR 3.0.3 79 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/award_swag.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/swags 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"message":"Enjoy this cool swag!"}}' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v0.13.0 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 201 23 | message: Created 24 | headers: 25 | Date: 26 | - Tue, 22 Aug 2017 15:09:44 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=d09e856041f6ae0c3a2a91e50ba326b211503414583; expires=Wed, 22-Aug-18 35 | 15:09:43 GMT; path=/; Domain=api.hackerone.com; HttpOnly 36 | X-Request-Id: 37 | - 8d9d9f70-ee1e-49a8-b396-0d763383d9e2 38 | Etag: 39 | - W/"31f75873e2b18f42b69b8d094d270f58" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | Content-Security-Policy: 45 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 46 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 47 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 48 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 49 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 50 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 51 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 52 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 53 | X-Content-Type-Options: 54 | - nosniff 55 | X-Download-Options: 56 | - noopen 57 | X-Frame-Options: 58 | - DENY 59 | X-Permitted-Cross-Domain-Policies: 60 | - none 61 | X-Xss-Protection: 62 | - 1; mode=block 63 | Public-Key-Pins-Report-Only: 64 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 65 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 66 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 67 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 68 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 69 | Server: 70 | - cloudflare-nginx 71 | Cf-Ray: 72 | - 3926c13b49050761-AMS 73 | body: 74 | encoding: UTF-8 75 | string: '{"data":{"id":"2057","type":"swag","attributes":{"sent":false,"created_at":"2017-08-22T15:09:44.176Z"}}}' 76 | http_version: 77 | recorded_at: Tue, 22 Aug 2017 15:09:43 GMT 78 | recorded_with: VCR 3.0.3 79 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/common_responses.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/programs/18969/common_responses?page%5Bnumber%5D=1&page%5Bsize%5D=100 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v0.13.0 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Mon, 28 Aug 2017 11:20:40 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=dafee7223f650cd1e244d455e37ea169f1503919239; expires=Tue, 28-Aug-18 35 | 11:20:39 GMT; path=/; Domain=api.hackerone.com; HttpOnly 36 | X-Request-Id: 37 | - 21d28136-7750-4557-83fb-e359b93a941b 38 | Etag: 39 | - W/"9b1e2aa1721b777df242b64b310c51bb" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | Content-Security-Policy: 45 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 46 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 47 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 48 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 49 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 50 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 51 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 52 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 53 | Referrer-Policy: 54 | - origin-when-cross-origin 55 | X-Content-Type-Options: 56 | - nosniff 57 | X-Download-Options: 58 | - noopen 59 | X-Frame-Options: 60 | - DENY 61 | X-Permitted-Cross-Domain-Policies: 62 | - none 63 | X-Xss-Protection: 64 | - 1; mode=block 65 | Public-Key-Pins-Report-Only: 66 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 67 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 68 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 69 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 70 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 71 | Server: 72 | - cloudflare-nginx 73 | Cf-Ray: 74 | - 3956e1efab7972f5-AMS 75 | body: 76 | encoding: ASCII-8BIT 77 | string: '{"data":[{"id":"108878","attributes":{"title":"Vulnerability Scanner 78 | False Positive","message":"Automated vulnerability scanners commonly have 79 | low priority issues and/or false positives. Before submitting the results 80 | from a scanner, please take a moment to confirm that the reported issues are 81 | actually valid and exploitable. Please reply if you have a working proof-of-concept 82 | or reason to believe that this issue is exploitable.\n"}},{"id":"108879","attributes":{"title":"No 83 | Security Implications","message":"Based on your initial description, there 84 | do not appear to be any security implications as a direct result of this behavior. 85 | If you disagree, please reply with additional information describing your 86 | reasoning. Including a working proof-of-concept can be incredibly helpful 87 | in our assessment of these claims.\n"}},{"id":"108880","attributes":{"title":"Language 88 | Barrier","message":"Sorry, I''m having a difficult time understanding this 89 | report. Please reply with a proof of concept and more technical details about 90 | the vulnerability, the impact of this vulnerability and any suggested fixes 91 | for this vulnerability. Including screenshots or a short video can be worth 92 | a thousand words. If you don''t speak English, feel free to leave your report 93 | in your own language, and we''ll try our best to find someone who can help 94 | translate.\n"}},{"id":"108881","attributes":{"title":"Logout cross-site request 95 | forgery","message":"For better or worse, the design of HTTP cookies means 96 | that no single website can prevent its users from being logged out; consequently, 97 | application-specific ways of achieving this goal will likely not qualify. 98 | You may be interested in personal blog posts from Chris Evans (https://scarybeastsecurity.blogspot.com/2010/01/logout-xsrf-significant-web-app-bug.html) 99 | and Michal Zalewski (https://lcamtuf.blogspot.com/2010/10/http-cookies-or-how-not-to-design.html) 100 | for more background.\n"}},{"id":"108882","attributes":{"title":"Open Redirect","message":"We 101 | recognize that the address bar is the only reliable security indicator in 102 | modern browsers. As a result, we typically do not treat arbitrary URL redirection 103 | behavior (\"Open Redirects\") as a security vulnerability unless you are able 104 | to demonstrate risks that do not depend upon social engineering.\n"}},{"id":"108883","attributes":{"title":"Strict-Transport-Security 105 | Not Necessary On This Domain","message":"Automated vulnerability scanners 106 | commonly have low priority issues and/or false positives. Before submitting 107 | the results from a scanner, please take a moment to confirm that the reported 108 | issues are actually valid and exploitable. In this specific case, the `Strict-Transport-Security` 109 | header is not suitable for the domain in question because it is intentionally 110 | accessible over both HTTP and HTTPS. If we ever migrate to 100% HTTPS on this 111 | domain, we''ll consider enabling the header at that time.\n"}},{"id":"108884","attributes":{"title":"Cookie 112 | Missing HttpOnly","message":"Automated vulnerability scanners commonly have 113 | low priority issues and/or false positives. Before submitting the results 114 | from a scanner, please take a moment to confirm that the reported issues are 115 | actually valid and exploitable. In this specific case, many cookies intentionally 116 | lack the `HttpOnly` flag so that they can be accessed from JavaScript. This 117 | only introduces a potential risk if the cookie in question contains session 118 | data or other sensitive information.\n"}},{"id":"108885","attributes":{"title":"Cookie 119 | Missing Secure","message":"Automated vulnerability scanners commonly have 120 | low priority issues and/or false positives. Before submitting the results 121 | from a scanner, please take a moment to confirm that the reported issues are 122 | actually valid and exploitable. In this specific case, many cookies intentionally 123 | lack the `secure` flag so that they can be accessed from HTTP pages. This 124 | only introduces a potential risk if the cookie in question contains sensitive 125 | information that must be served over HTTPS.\n"}},{"id":"108886","attributes":{"title":"X-XSS-Protection","message":"Automated 126 | vulnerability scanners commonly have low priority issues and/or false positives. 127 | Before submitting the results from a scanner, please take a moment to confirm 128 | that the reported issues are actually valid and exploitable. In this specific 129 | case, we believe that the default state of the `X-XSS-Protection` header is 130 | sufficient for our purposes. Please reply if you have a working proof-of-concept 131 | that could be mitigated by an adjustment to our header.\n"}},{"id":"108887","attributes":{"title":"X-Content-Type-Options: 132 | nosniff","message":"Automated vulnerability scanners commonly have low priority 133 | issues and/or false positives. Before submitting the results from a scanner, 134 | please take a moment to confirm that the reported issues are actually valid 135 | and exploitable. In this specific case, the `X-Content-Type-Options: nosniff` 136 | header is only necessary on endpoints that serve untrusted user content. Please 137 | reply if you have a working proof-of-concept or reason to believe that this 138 | issue is exploitable.\n"}},{"id":"108888","attributes":{"title":"X-Frame-Options 139 | / Clickjacking","message":"The lack of X-Frame-Options does not always indicate 140 | that a security vulnerability is present. This is an optional header that 141 | is only necessary on endpoints where there UI is rendered to invoke state 142 | changing actions. We recommend reading this informative post by David Ross: 143 | https://plus.google.com/u/0/+DavidRossX/posts/jVrtTRd5yKP\n"}},{"id":"108889","attributes":{"title":"Autocomplete","message":"We 144 | intentionally leave autocomplete enabled as we believe that all modern browsers 145 | now handle local form completion in a reasonably sane manner. Autocomplete 146 | enables individuals to use stronger passwords and makes them less susceptible 147 | to phishing attacks. These benefits greatly outweigh the minor risk here. 148 | If you disagree, we encourage you to also read this post: https://blog.0xbadc0de.be/archives/124\n"}},{"id":"108890","attributes":{"title":"SSL 149 | - RC4 / BEAST Information","message":"Automated vulnerability scanners commonly 150 | have low priority issues and/or false positives. Before submitting the results 151 | from a scanner, please take a moment to confirm that the reported issues are 152 | actually valid and exploitable. In this instance, we intentionally use RC4 153 | when the client is connecting with TLS 1.0 and earlier as an effective mitigation 154 | against the \"BEAST\" attack. For clients that support TLS 1.1 and higher, 155 | we prioritize non-RC4 ciphers. We believe that this is consistent with current 156 | industry best practices. For more information, please review this post: https://blog.cloudflare.com/killing-rc4\nThis 157 | combination most effectively balances the competing risks associated with 158 | weaker RC4 ciphers and the BEAST attack scenario.\n"}},{"id":"108891","attributes":{"title":"Video 159 | Without Content","message":"Using a video to demonstrate a potential issue 160 | should only be necessary in rare situations and should always be accompanied 161 | with a text description of the issue as well. Please update this report with 162 | step-by-step instructions to reproduce the core components of the issue. If 163 | you don''t speak English, feel free to leave your report in your own language, 164 | and we''ll try our best to find someone who can help translate.\n"}}],"links":{}}' 165 | http_version: 166 | recorded_at: Mon, 28 Aug 2017 11:20:40 GMT 167 | recorded_with: VCR 3.0.3 168 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/create_report_invalid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"report","attributes":{"team_handle":"github","title":"hi","vulnerability_information":"hi","impact":"string","severity_rating":"invalid_severity","source":"api"}}}' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v1.0.1 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 400 23 | message: Bad Request 24 | headers: 25 | Date: 26 | - Wed, 06 May 2020 23:18:54 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=de6cf45714928bec4f073b32dab8dddec1588807134; expires=Fri, 05-Jun-20 35 | 23:18:54 GMT; path=/; Domain=api.hackerone.com; HttpOnly; SameSite=Lax; Secure 36 | X-Request-Id: 37 | - ef6a977c-7273-4682-86ac-97a6af3d8ba0 38 | Cache-Control: 39 | - no-cache 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | X-Frame-Options: 43 | - DENY 44 | X-Content-Type-Options: 45 | - nosniff 46 | X-Xss-Protection: 47 | - 1; mode=block 48 | X-Download-Options: 49 | - noopen 50 | X-Permitted-Cross-Domain-Policies: 51 | - none 52 | Referrer-Policy: 53 | - strict-origin-when-cross-origin 54 | Expect-Ct: 55 | - enforce, max-age=86400 56 | Content-Security-Policy: 57 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 58 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 59 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 60 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 61 | profile-photos.hackerone-user-content.com hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 62 | media-src ''self'' hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 63 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 64 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 65 | Cf-Cache-Status: 66 | - DYNAMIC 67 | Server: 68 | - cloudflare 69 | Cf-Ray: 70 | - 58f66c4e6cd3f56d-SEA 71 | Cf-Request-Id: 72 | - '028de204fe0000f56dae94e200000001' 73 | body: 74 | encoding: UTF-8 75 | string: '{"errors":[{"status":400,"title":"Invalid Parameter","detail":"The 76 | parameter ''severity_rating'' is invalid.","source":{"parameter":"severity_rating"}}]}' 77 | http_version: null 78 | recorded_at: Wed, 06 May 2020 23:18:54 GMT 79 | recorded_with: VCR 5.1.0 80 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/empty_report_list.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/reports?filter%5Bcreated_at__gt%5D=2017-02-11T16:00:44-10:00&filter%5Bprogram%5D%5B0%5D=github&filter%5Bstate%5D%5B0%5D=new 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic 123= 12 | User-Agent: 13 | - Faraday v0.11.0 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Wed, 15 Feb 2017 02:00:44 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=123; expires=Thu, 15-Feb-18 33 | 02:00:44 GMT; path=/; Domain=api.hackerone.com; HttpOnly 34 | X-Request-Id: 35 | - ae9893cb-0d8c-4481-86da-a5b2622e3b3c 36 | Etag: 37 | - W/"2f7f00d7f6925232d6ce911820aca337" 38 | Cache-Control: 39 | - max-age=0, private, must-revalidate 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 44 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 45 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Download-Options: 49 | - noopen 50 | X-Frame-Options: 51 | - DENY 52 | X-Permitted-Cross-Domain-Policies: 53 | - none 54 | X-Xss-Protection: 55 | - 1; mode=block 56 | Public-Key-Pins-Report-Only: 57 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 58 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 59 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 60 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 61 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 62 | Server: 63 | - cloudflare-nginx 64 | Cf-Ray: 65 | - 33152afcd96b5414-LAX 66 | body: 67 | encoding: ASCII-8BIT 68 | string: '{"data":[],"links":{}}' 69 | http_version: 70 | recorded_at: Wed, 15 Feb 2017 02:00:44 GMT 71 | recorded_with: VCR 3.0.3 72 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/get_balance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/programs/18969/billing/balance 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v0.17.3 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Fri, 12 Feb 2021 20:37:48 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=d64ff625fe5f42c2547caf518714b001c1613162268; expires=Sun, 14-Mar-21 35 | 20:37:48 GMT; path=/; Domain=api.hackerone.com; HttpOnly; SameSite=Lax; Secure 36 | X-Request-Id: 37 | - 49e5c329-cf7f-4723-9853-a47db4c6ef4d 38 | Etag: 39 | - W/"623ba11e90acdaa5bfcf60eb7743af10" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | X-Frame-Options: 45 | - DENY 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Xss-Protection: 49 | - 1; mode=block 50 | X-Download-Options: 51 | - noopen 52 | X-Permitted-Cross-Domain-Policies: 53 | - none 54 | Referrer-Policy: 55 | - strict-origin-when-cross-origin 56 | Expect-Ct: 57 | - enforce, max-age=86400 58 | Content-Security-Policy: 59 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 60 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 61 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 62 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 63 | profile-photos.hackerone-user-content.com hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 64 | media-src ''self'' hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 65 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 66 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=374aea95847f4040a69f9c8d49a3a59d' 67 | Cf-Cache-Status: 68 | - DYNAMIC 69 | Cf-Request-Id: 70 | - '08398fde1c000027e4d90cd000000001' 71 | Server: 72 | - cloudflare 73 | Cf-Ray: 74 | - 62091c102ef927e4-SLC 75 | body: 76 | encoding: ASCII-8BIT 77 | string: '{"data":{"id":"1894","type":"program-balance","attributes":{"balance":"118386.40"}}}' 78 | http_version: 79 | recorded_at: Fri, 12 Feb 2021 20:37:48 GMT 80 | recorded_with: VCR 3.0.3 81 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/missing_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/reports/404 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic 123 12 | User-Agent: 13 | - Faraday v0.11.0 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 403 21 | message: Forbidden 22 | headers: 23 | Date: 24 | - Wed, 15 Feb 2017 01:01:18 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=123; expires=Thu, 15-Feb-18 01:01:17 GMT; path=/; Domain=api.hackerone.com; 33 | HttpOnly 34 | X-Request-Id: 35 | - 21757437-7a32-4560-af4a-885b09f20381 36 | Cache-Control: 37 | - no-cache 38 | Strict-Transport-Security: 39 | - max-age=31536000; includeSubDomains; preload 40 | Content-Security-Policy: 41 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 42 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 43 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 44 | X-Content-Type-Options: 45 | - nosniff 46 | X-Download-Options: 47 | - noopen 48 | X-Frame-Options: 49 | - DENY 50 | X-Permitted-Cross-Domain-Policies: 51 | - none 52 | X-Xss-Protection: 53 | - 1; mode=block 54 | Public-Key-Pins-Report-Only: 55 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 56 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 57 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 58 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 59 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 60 | Server: 61 | - cloudflare-nginx 62 | Cf-Ray: 63 | - 3314d3eb0b6653a8-LAX 64 | body: 65 | encoding: ASCII-8BIT 66 | string: '{"errors":[{"status":403}]}' 67 | http_version: 68 | recorded_at: Wed, 15 Feb 2017 01:01:18 GMT 69 | - request: 70 | method: post 71 | uri: https://api.hackerone.com/v1/reports/4040000000000000/state_changes 72 | body: 73 | encoding: UTF-8 74 | string: '{"data":{"type":"state-change","attributes":{"message":"This is has 75 | been triaged internally.","state":"triaged"}}}' 76 | headers: 77 | Authorization: 78 | - Basic nope 79 | User-Agent: 80 | - Faraday v0.11.0 81 | Content-Type: 82 | - application/json 83 | Accept-Encoding: 84 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 85 | Accept: 86 | - "*/*" 87 | response: 88 | status: 89 | code: 400 90 | message: Bad Request 91 | headers: 92 | Date: 93 | - Thu, 16 Mar 2017 20:06:07 GMT 94 | Content-Type: 95 | - application/json; charset=utf-8 96 | Transfer-Encoding: 97 | - chunked 98 | Connection: 99 | - keep-alive 100 | Set-Cookie: 101 | - __cfduid=d5e022e1aea468d8848bf0720edab1cae1489694766; expires=Fri, 16-Mar-18 102 | 20:06:06 GMT; path=/; Domain=api.hackerone.com; HttpOnly 103 | X-Request-Id: 104 | - 73b52183-e64f-45db-bcc4-11d46a021364 105 | Cache-Control: 106 | - no-cache 107 | Strict-Transport-Security: 108 | - max-age=31536000; includeSubDomains; preload 109 | Content-Security-Policy: 110 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 111 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 112 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 113 | X-Content-Type-Options: 114 | - nosniff 115 | X-Download-Options: 116 | - noopen 117 | X-Frame-Options: 118 | - DENY 119 | X-Permitted-Cross-Domain-Policies: 120 | - none 121 | X-Xss-Protection: 122 | - 1; mode=block 123 | Public-Key-Pins-Report-Only: 124 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 125 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 126 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 127 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 128 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 129 | Server: 130 | - cloudflare-nginx 131 | Cf-Ray: 132 | - 340a54c4fbc92240-LAX 133 | body: 134 | encoding: UTF-8 135 | string: '{"errors":[{"status":400,"title":"Invalid Parameter","detail":"The 136 | parameter ''report_id'' is invalid.","source":{"parameter":"report_id"}}]}' 137 | http_version: 138 | recorded_at: Thu, 16 Mar 2017 20:06:07 GMT 139 | - request: 140 | method: post 141 | uri: https://api.hackerone.com/v1/reports/4040000000000000/issue_tracker_reference_id 142 | body: 143 | encoding: UTF-8 144 | string: '{"data":{"type":"issue-tracker-reference-id","attributes":{"reference":"fooooo"}}}' 145 | headers: 146 | Authorization: 147 | - Basic nope 148 | User-Agent: 149 | - Faraday v0.11.0 150 | Content-Type: 151 | - application/json 152 | Accept-Encoding: 153 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 154 | Accept: 155 | - "*/*" 156 | response: 157 | status: 158 | code: 400 159 | message: Bad Request 160 | headers: 161 | Date: 162 | - Thu, 16 Mar 2017 20:23:27 GMT 163 | Content-Type: 164 | - application/json; charset=utf-8 165 | Transfer-Encoding: 166 | - chunked 167 | Connection: 168 | - keep-alive 169 | Set-Cookie: 170 | - __cfduid=d937ff918b1e20c9491cb8b44ba307c1d1489695807; expires=Fri, 16-Mar-18 171 | 20:23:27 GMT; path=/; Domain=api.hackerone.com; HttpOnly 172 | X-Request-Id: 173 | - 80d19f89-5fc0-412a-b30a-6caaad350d0c 174 | Cache-Control: 175 | - no-cache 176 | Strict-Transport-Security: 177 | - max-age=31536000; includeSubDomains; preload 178 | Content-Security-Policy: 179 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 180 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 181 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 182 | X-Content-Type-Options: 183 | - nosniff 184 | X-Download-Options: 185 | - noopen 186 | X-Frame-Options: 187 | - DENY 188 | X-Permitted-Cross-Domain-Policies: 189 | - none 190 | X-Xss-Protection: 191 | - 1; mode=block 192 | Public-Key-Pins-Report-Only: 193 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 194 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 195 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 196 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 197 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 198 | Server: 199 | - cloudflare-nginx 200 | Cf-Ray: 201 | - 340a6e2b3a0522c4-LAX 202 | body: 203 | encoding: UTF-8 204 | string: '{"errors":[{"status":400,"title":"Invalid Parameter","detail":"The 205 | parameter ''report_id'' is invalid.","source":{"parameter":"report_id"}}]}' 206 | http_version: 207 | recorded_at: Thu, 16 Mar 2017 20:23:27 GMT 208 | recorded_with: VCR 3.0.3 209 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/programs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/me/programs 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic nope 12 | User-Agent: 13 | - Faraday v0.12.0.1 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Sun, 09 Apr 2017 09:10:54 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=d5d9dea364499e21060bc94e1a78d63211491729053; expires=Mon, 09-Apr-18 33 | 09:10:53 GMT; path=/; Domain=api.hackerone.com; HttpOnly 34 | X-Request-Id: 35 | - 4604197f-b50f-4813-a964-644a4c52ba35 36 | Etag: 37 | - W/"9329ef6b8f370e76d96d71977a6480c4" 38 | Cache-Control: 39 | - max-age=0, private, must-revalidate 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 44 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 45 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Download-Options: 49 | - noopen 50 | X-Frame-Options: 51 | - DENY 52 | X-Permitted-Cross-Domain-Policies: 53 | - none 54 | X-Xss-Protection: 55 | - 1; mode=block 56 | Public-Key-Pins-Report-Only: 57 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 58 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 59 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 60 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 61 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 62 | Server: 63 | - cloudflare-nginx 64 | Cf-Ray: 65 | - 34cc55fa4aa8731f-AMS 66 | body: 67 | encoding: ASCII-8BIT 68 | string: '{"data":[{"id":"18969","type":"program","attributes":{"handle":"github","created_at":"2017-03-08T19:22:41.884Z","updated_at":"2017-04-09T08:15:15.228Z"}}],"links":{}}' 69 | http_version: 70 | recorded_at: Sun, 09 Apr 2017 09:10:40 GMT 71 | recorded_with: VCR 3.0.3 72 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/report_list.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/reports?filter%5Bcreated_at__gt%5D=2017-02-11T16:00:44-10:00&filter%5Bprogram%5D%5B0%5D=github&filter%5Bstate%5D%5B0%5D=new 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic nope= 12 | User-Agent: 13 | - Faraday v0.11.0 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Sat, 18 Feb 2017 19:16:35 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=123; expires=Sun, 18-Feb-18 33 | 19:16:35 GMT; path=/; Domain=api.hackerone.com; HttpOnly 34 | X-Request-Id: 35 | - 123 36 | Etag: 37 | - W/"e337505dbc57f7e1f85685911c939b6e" 38 | Cache-Control: 39 | - max-age=0, private, must-revalidate 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 44 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 45 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Download-Options: 49 | - noopen 50 | X-Frame-Options: 51 | - DENY 52 | X-Permitted-Cross-Domain-Policies: 53 | - none 54 | X-Xss-Protection: 55 | - 1; mode=block 56 | Public-Key-Pins-Report-Only: 57 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 58 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 59 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 60 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 61 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 62 | Server: 63 | - cloudflare-nginx 64 | Cf-Ray: 65 | - 3333d0794c920d4f-LAX 66 | body: 67 | encoding: ASCII-8BIT 68 | string: '{ 69 | "data": [ 70 | { 71 | "id": "207385", 72 | "type": "report", 73 | "attributes": { 74 | "title": "What is my purpose", 75 | "state": "new", 76 | "created_at": "2017-02-18T18:26:13.283Z", 77 | "vulnerability_information": "You pass the butter", 78 | "triaged_at": null, 79 | "closed_at": null, 80 | "last_reporter_activity_at": "2017-02-18T18:26:13.387Z", 81 | "first_program_activity_at": null, 82 | "last_program_activity_at": null, 83 | "bounty_awarded_at": null, 84 | "swag_awarded_at": null, 85 | "disclosed_at": null, 86 | "last_activity_at": "2017-02-18T18:26:13.387Z" 87 | }, 88 | "relationships": { 89 | "reporter": { 90 | "data": { 91 | "id": "123", 92 | "type": "user", 93 | "attributes": { 94 | "username": "rickestofallthericks", 95 | "name": "Rick Sanchez", 96 | "disabled": false, 97 | "created_at": "2017-02-18T18:21:28.638Z", 98 | "profile_picture": { 99 | "62x62": "/assets/avatars/default-123.png", 100 | "82x82": "/assets/avatars/default-123.png", 101 | "110x110": "/assets/avatars/default-123.png", 102 | "260x260": "/assets/avatars/default-123.png" 103 | } 104 | } 105 | } 106 | }, 107 | "program": { 108 | "data": { 109 | "id": "1894", 110 | "type": "program", 111 | "attributes": { 112 | "handle": "github", 113 | "created_at": "2015-05-29T20:12:03.091Z", 114 | "updated_at": "2017-02-17T15:18:04.114Z" 115 | } 116 | } 117 | }, 118 | "severity": { 119 | "data": { 120 | "id": "26662", 121 | "type": "severity", 122 | "attributes": { 123 | "rating": "critical", 124 | "author_type": "User", 125 | "user_id": 123, 126 | "created_at": "2017-02-18T18:26:13.373Z", 127 | "score": 9.1, 128 | "attack_complexity": "low", 129 | "attack_vector": "network", 130 | "availability": "none", 131 | "confidentiality": "high", 132 | "integrity": "high", 133 | "privileges_required": "none", 134 | "user_interaction": "none", 135 | "scope": "unchanged" 136 | } 137 | } 138 | }, 139 | "vulnerability_types": { 140 | "data": [ 141 | { 142 | "id": "25110", 143 | "type": "vulnerability-type", 144 | "attributes": { 145 | "name": "Information Disclosure", 146 | "description": "nope", 147 | "created_at": "2016-01-28T13:34:08.945Z" 148 | } 149 | } 150 | ] 151 | }, 152 | "bounties": { 153 | "data": [] 154 | } 155 | } 156 | }, 157 | { 158 | "id": "207228", 159 | "type": "report", 160 | "attributes": { 161 | "title": "Take 2 Strokes off of Jerry''s swing", 162 | "state": "new", 163 | "created_at": "2017-02-17T21:51:00.916Z", 164 | "vulnerability_information": "Hai", 165 | "triaged_at": null, 166 | "closed_at": null, 167 | "last_reporter_activity_at": "2017-02-17T21:51:01.110Z", 168 | "first_program_activity_at": null, 169 | "last_program_activity_at": null, 170 | "bounty_awarded_at": null, 171 | "swag_awarded_at": null, 172 | "disclosed_at": null, 173 | "last_activity_at": "2017-02-17T21:51:01.110Z" 174 | }, 175 | "relationships": { 176 | "reporter": { 177 | "data": { 178 | "id": "1234", 179 | "type": "user", 180 | "attributes": { 181 | "username": "ImMrMeeseeks", 182 | "name": "Mr Meeseeks", 183 | "disabled": false, 184 | "created_at": "2017-02-17T20:52:56.666Z", 185 | "profile_picture": { 186 | "62x62": "/assets/avatars/default-123.png", 187 | "82x82": "/assets/avatars/default-123.png", 188 | "110x110": "/assets/avatars/default-123.png", 189 | "260x260": "/assets/avatars/default-123.png" 190 | } 191 | } 192 | } 193 | }, 194 | "program": { 195 | "data": { 196 | "id": "1894", 197 | "type": "program", 198 | "attributes": { 199 | "handle": "github", 200 | "created_at": "2015-05-29T20:12:03.091Z", 201 | "updated_at": "2017-02-17T15:18:04.114Z" 202 | } 203 | } 204 | }, 205 | "severity": { 206 | "data": { 207 | "id": "26541", 208 | "type": "severity", 209 | "attributes": { 210 | "rating": "low", 211 | "author_type": "User", 212 | "user_id": 144214, 213 | "created_at": "2017-02-17T21:51:01.093Z" 214 | } 215 | } 216 | }, 217 | "vulnerability_types": { 218 | "data": [ 219 | { 220 | "id": "25110", 221 | "type": "vulnerability-type", 222 | "attributes": { 223 | "name": "Information Disclosure", 224 | "description": "Exposure of system information, sensitive or private information, fingerprinting, etc.\n", 225 | "created_at": "2016-01-28T13:34:08.945Z" 226 | } 227 | } 228 | ] 229 | }, 230 | "bounties": { 231 | "data": [] 232 | } 233 | } 234 | } 235 | ], 236 | "links": {} 237 | }' 238 | http_version: 239 | recorded_at: Sat, 18 Feb 2017 19:16:35 GMT 240 | recorded_with: VCR 3.0.3 241 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/server_error.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/reports/500 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic nope= 12 | User-Agent: 13 | - Faraday v0.11.0 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 500 21 | message: Error 22 | headers: 23 | Date: 24 | - Wed, 15 Feb 2017 00:50:01 GMT 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Transfer-Encoding: 28 | - chunked 29 | Connection: 30 | - keep-alive 31 | Set-Cookie: 32 | - __cfduid=123; expires=Thu, 15-Feb-18 33 | 00:50:01 GMT; path=/; Domain=api.hackerone.com; HttpOnly 34 | X-Request-Id: 35 | - 345 36 | Etag: 37 | - W/"8dfc97642d70f82b560e989d44e19eac" 38 | Cache-Control: 39 | - max-age=0, private, must-revalidate 40 | Strict-Transport-Security: 41 | - max-age=31536000; includeSubDomains; preload 42 | Content-Security-Policy: 43 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 44 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 45 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=123 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Download-Options: 49 | - noopen 50 | X-Frame-Options: 51 | - DENY 52 | X-Permitted-Cross-Domain-Policies: 53 | - none 54 | X-Xss-Protection: 55 | - 1; mode=block 56 | Public-Key-Pins-Report-Only: 57 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 58 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 59 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 60 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 61 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 62 | Server: 63 | - cloudflare-nginx 64 | Cf-Ray: 65 | - 3314c3653e302126-LAX 66 | body: 67 | encoding: ASCII-8BIT 68 | string: '{ 69 | "data": { 70 | "error": "fail" 71 | } 72 | }' 73 | http_version: 74 | recorded_at: Wed, 15 Feb 2017 00:50:01 GMT 75 | recorded_with: VCR 3.0.3 76 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/stage_change.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/state_changes 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"state-change","attributes":{"message":"This is has 9 | been triaged internally.","state":"triaged"}}}' 10 | headers: 11 | Authorization: 12 | - Basic nope 13 | User-Agent: 14 | - Faraday v0.11.0 15 | Content-Type: 16 | - application/json 17 | Accept-Encoding: 18 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 19 | Accept: 20 | - "*/*" 21 | response: 22 | status: 23 | code: 201 24 | message: Created 25 | headers: 26 | Date: 27 | - Thu, 16 Mar 2017 19:53:50 GMT 28 | Content-Type: 29 | - application/json; charset=utf-8 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - __cfduid=d6abf05e4448cfd8e6f406e1fb105f1911489694029; expires=Fri, 16-Mar-18 36 | 19:53:49 GMT; path=/; Domain=api.hackerone.com; HttpOnly 37 | X-Request-Id: 38 | - cd191af9-7666-4ed1-8a52-8a1305e29f5e 39 | Etag: 40 | - W/"ec64e6720b3ad3ce4838299e5a951f99" 41 | Cache-Control: 42 | - max-age=0, private, must-revalidate 43 | Strict-Transport-Security: 44 | - max-age=31536000; includeSubDomains; preload 45 | Content-Security-Policy: 46 | - default-src 'none'; connect-src 'self' www.google-analytics.com errors.hackerone.net; 47 | font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 48 | 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598 49 | X-Content-Type-Options: 50 | - nosniff 51 | X-Download-Options: 52 | - noopen 53 | X-Frame-Options: 54 | - DENY 55 | X-Permitted-Cross-Domain-Policies: 56 | - none 57 | X-Xss-Protection: 58 | - 1; mode=block 59 | Public-Key-Pins-Report-Only: 60 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 61 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 62 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 63 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 64 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 65 | Server: 66 | - cloudflare-nginx 67 | Cf-Ray: 68 | - 340a42c58e9553de-LAX 69 | body: 70 | encoding: UTF-8 71 | string: '{"data":{"id":"200","type":"report","attributes":{"title":"ssss","state":"triaged","created_at":"2016-04-18T22:24:50.065Z","vulnerability_information":"sssss","triaged_at":"2017-03-16T19:53:49.939Z","closed_at":null,"last_reporter_activity_at":"2016-04-18T22:24:50.118Z","first_program_activity_at":"2017-03-16T18:36:40.650Z","last_program_activity_at":"2017-03-16T19:53:49.939Z","bounty_awarded_at":null,"swag_awarded_at":null,"disclosed_at":null,"last_activity_at":"2017-03-16T19:53:49.939Z","issue_tracker_reference_id":"3476"},"relationships":{"reporter":{"data":{"id":"57690","type":"user","attributes":{"username":"ndm-github","name":"Neil 72 | Matatall","disabled":false,"created_at":"2016-02-24T01:33:01.258Z","profile_picture":{"62x62":"https://profile-photos.hackerone-user-content.com/production/000/057/690/1e0c9ef6fc8bcc17806ae82e6f73cdd4d0e74eb9_small.jpg?1469554487","82x82":"https://profile-photos.hackerone-user-content.com/production/000/057/690/f6a17c40a6c910ba801014d1498b55727ea858e3_medium.jpg?1469554487","110x110":"https://profile-photos.hackerone-user-content.com/production/000/057/690/2259dde15230756d99f68a9ca824af11081ab965_large.jpg?1469554487","260x260":"https://profile-photos.hackerone-user-content.com/production/000/057/690/6d2da33805fef8b8ac4cf513e1562699e79365e0_xtralarge.jpg?1469554487"}}}},"program":{"data":{"id":"11767","type":"program","attributes":{"handle":"github-test","created_at":"2016-04-15T17:10:31.261Z","updated_at":"2016-09-20T14:54:15.448Z"}}},"swag":{"data":[]},"attachments":{"data":[]},"vulnerability_types":{"data":[{"id":"107921","type":"vulnerability-type","attributes":{"name":"Cross-Site 73 | Scripting (XSS)","description":"Failure of a site to validate, filter, or 74 | encode user input before returning it to another user''s web client.\n","created_at":"2016-04-15T17:10:39.169Z"}}]},"activities":{"data":[{"type":"activity-bug-triaged","id":"1546376","attributes":{"message":"This 75 | is has been triaged internally.","created_at":"2017-03-16T19:53:49.939Z","updated_at":"2017-03-16T19:53:49.939Z","internal":false},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"type":"activity-reference-id-added","id":"1546123","attributes":{"message":"","created_at":"2017-03-16T18:36:49.045Z","updated_at":"2017-03-16T18:36:49.045Z","internal":true,"reference":"3476","reference_url":null},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}},{"type":"activity-bug-needs-more-info","id":"1546120","attributes":{"message":"This 76 | is has been triaged internally.","created_at":"2017-03-16T18:36:40.650Z","updated_at":"2017-03-16T18:36:40.650Z","internal":false},"relationships":{"actor":{"data":{"type":"user","id":"151303","attributes":{"username":"testingagain","name":null,"disabled":false,"created_at":"2017-03-16T00:35:19.472Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}}]},"bounties":{"data":[]},"summaries":{"data":[]}}}}' 77 | http_version: 78 | recorded_at: Thu, 16 Mar 2017 19:53:50 GMT 79 | recorded_with: VCR 3.0.3 80 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/suggest_a_bounty.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/bounty_suggestions 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"message":"This report is great, I think we should award a 9 | high bounty.","amount":5000,"bonus_amount":2500}}' 10 | headers: 11 | Authorization: 12 | - Basic NOPE 13 | User-Agent: 14 | - Faraday v0.13.0 15 | Content-Type: 16 | - application/json 17 | Accept-Encoding: 18 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 19 | Accept: 20 | - "*/*" 21 | response: 22 | status: 23 | code: 201 24 | message: Created 25 | headers: 26 | Date: 27 | - Tue, 22 Aug 2017 15:10:02 GMT 28 | Content-Type: 29 | - application/json; charset=utf-8 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - __cfduid=d024b34c2f975a4ee9ede2a5bc288fdc11503414602; expires=Wed, 22-Aug-18 36 | 15:10:02 GMT; path=/; Domain=api.hackerone.com; HttpOnly 37 | X-Request-Id: 38 | - 6114bb66-4dad-4bb0-8913-530c38758156 39 | Etag: 40 | - W/"554f310fcd9c49f5d069ea686e38e8e2" 41 | Cache-Control: 42 | - max-age=0, private, must-revalidate 43 | Strict-Transport-Security: 44 | - max-age=31536000; includeSubDomains; preload 45 | Content-Security-Policy: 46 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 47 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 48 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 49 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 50 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 51 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 52 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 53 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 54 | X-Content-Type-Options: 55 | - nosniff 56 | X-Download-Options: 57 | - noopen 58 | X-Frame-Options: 59 | - DENY 60 | X-Permitted-Cross-Domain-Policies: 61 | - none 62 | X-Xss-Protection: 63 | - 1; mode=block 64 | Public-Key-Pins-Report-Only: 65 | - pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; 66 | pin-sha256="K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; pin-sha256="iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; 67 | pin-sha256="cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; pin-sha256="bIlWcjiKq1mftH/xd7Hw1JO77Cr+Gv+XYcGUQWwO+A4="; 68 | pin-sha256="tXD+dGAP8rGY4PW1be90cOYEwg7pZ4G+yPZmIZWPTSg="; max-age=600; includeSubDomains; 69 | report-uri="https://hackerone.report-uri.io/r/default/hpkp/reportOnly" 70 | Server: 71 | - cloudflare-nginx 72 | Cf-Ray: 73 | - 3926c1aecfdb2c72-AMS 74 | body: 75 | encoding: UTF-8 76 | string: '{"data":{"type":"activity-bounty-suggested","id":"1946481","attributes":{"message":"This 77 | report is great, I think we should award a high bounty.","created_at":"2017-08-22T15:10:02.699Z","updated_at":"2017-08-22T15:10:02.699Z","internal":true,"bounty_amount":"5,000","bonus_amount":"2,500"},"relationships":{"actor":{"data":{"type":"user","id":"193855","attributes":{"username":"sjors","name":null,"disabled":false,"created_at":"2017-08-22T13:18:29.084Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"}}}}}}}' 78 | http_version: 79 | recorded_at: Tue, 22 Aug 2017 15:10:02 GMT 80 | recorded_with: VCR 3.0.3 81 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/swag_sent.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: put 5 | uri: https://api.hackerone.com/v1/programs/18969/swag/3377 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"swag","attributes":{"sent":true}}}' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v0.17.0 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Mon, 21 Oct 2019 22:00:10 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=d462d381b66fd12f1206f12f1234d8e3d1571695209; expires=Tue, 20-Oct-20 35 | 22:00:09 GMT; path=/; Domain=api.hackerone.com; HttpOnly; Secure 36 | X-Request-Id: 37 | - b5416764-3419-4a27-bd36-9cce7e6f4aa7 38 | Etag: 39 | - W/"2e21cfa10ed9d144353542ef658979e8" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | Expect-Ct: 45 | - enforce, max-age=86400 46 | Content-Security-Policy: 47 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 48 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 49 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 50 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 51 | profile-photos.hackerone-user-content.com hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 52 | media-src ''self'' hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 53 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 54 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 55 | Referrer-Policy: 56 | - strict-origin-when-cross-origin 57 | X-Content-Type-Options: 58 | - nosniff 59 | X-Download-Options: 60 | - noopen 61 | X-Frame-Options: 62 | - DENY 63 | X-Permitted-Cross-Domain-Policies: 64 | - none 65 | X-Xss-Protection: 66 | - 1; mode=block 67 | Cf-Cache-Status: 68 | - DYNAMIC 69 | Server: 70 | - cloudflare 71 | Cf-Ray: 72 | - 529680b549577fd8-SAN 73 | body: 74 | encoding: ASCII-8BIT 75 | string: '{"id":"3377","type":"swag","attributes":{"sent":true,"created_at":"2018-12-13T04:06:32.994Z"},"relationships":{"user":{"data":{"id":"175595","type":"user","attributes":{"username":"anglinb_x0rsd","name":"Brian 76 | Anglin","disabled":false,"created_at":"2017-06-13T18:42:14.025Z","profile_picture":{"62x62":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","82x82":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","110x110":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png","260x260":"/assets/avatars/default-71a302d706457f3d3a31eb30fa3e73e6cf0b1d677b8fa218eaeaffd67ae97918.png"},"signal":null,"impact":null,"reputation":null,"bio":null,"website":null,"location":null,"hackerone_triager":false}}},"address":{"data":{"id":"7374","type":"address","attributes":{"name":"Brian 77 | Anglin","street":" 88 Colin P Kelly Jr Street","city":"San Francisco","postal_code":"94107","state":"California","country":"United 78 | States","created_at":"2019-10-21T20:50:40.159Z","tshirt_size":"M_Medium","phone_number":"555-555-5555"}}}}}' 79 | http_version: 80 | recorded_at: Mon, 21 Oct 2019 22:00:10 GMT 81 | recorded_with: VCR 3.0.3 82 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/update_policy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: put 5 | uri: https://api.hackerone.com/v1/programs/18969/policy 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"program-policy","attributes":{"policy":"Hello World, 9 | updating policy"}}}' 10 | headers: 11 | Authorization: 12 | - Basic NOPE 13 | User-Agent: 14 | - Faraday v1.0.0 15 | Content-Type: 16 | - application/json 17 | Accept-Encoding: 18 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 19 | Accept: 20 | - "*/*" 21 | response: 22 | status: 23 | code: 200 24 | message: OK 25 | headers: 26 | Date: 27 | - Mon, 09 Mar 2020 20:21:52 GMT 28 | Content-Type: 29 | - application/json; charset=utf-8 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - __cfduid=d945343ceedab8854528404d5a2d2e02c1583785311; expires=Wed, 08-Apr-20 36 | 20:21:51 GMT; path=/; Domain=api.hackerone.com; HttpOnly; SameSite=Lax; Secure 37 | X-Request-Id: 38 | - d092d577-a1c3-4c26-a958-184f1712c016 39 | Etag: 40 | - W/"350f99241c904a842cf99ff4f20f5abb" 41 | Cache-Control: 42 | - max-age=0, private, must-revalidate 43 | Strict-Transport-Security: 44 | - max-age=31536000; includeSubDomains; preload 45 | X-Frame-Options: 46 | - DENY 47 | X-Content-Type-Options: 48 | - nosniff 49 | X-Xss-Protection: 50 | - 1; mode=block 51 | X-Download-Options: 52 | - noopen 53 | X-Permitted-Cross-Domain-Policies: 54 | - none 55 | Referrer-Policy: 56 | - strict-origin-when-cross-origin 57 | Expect-Ct: 58 | - enforce, max-age=86400 59 | Content-Security-Policy: 60 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 61 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 62 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 63 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 64 | profile-photos.hackerone-user-content.com hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 65 | media-src ''self'' hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 66 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 67 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 68 | Cf-Cache-Status: 69 | - DYNAMIC 70 | Server: 71 | - cloudflare 72 | Cf-Ray: 73 | - 571781356864e39e-ATL 74 | body: 75 | encoding: ASCII-8BIT 76 | string: '{"data":{"id":"18969","type":"program","attributes":{"handle":"github","policy":"Hello 77 | World, updating policy","created_at":"2016-04-15T17:10:31.261Z","updated_at":"2020-03-09T20:21:51.983Z"}}}' 78 | http_version: null 79 | recorded_at: Mon, 09 Mar 2020 20:21:52 GMT 80 | recorded_with: VCR 5.1.0 81 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/update_severity.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.hackerone.com/v1/reports/200/severities 6 | body: 7 | encoding: UTF-8 8 | string: '{"data":{"type":"severity","attributes":{"rating":"high"}}}' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v1.0.0 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Mon, 23 Mar 2020 22:11:50 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=defc457f4849bd5da92f0d0a9e21f70cf1585001510; expires=Wed, 22-Apr-20 35 | 22:11:50 GMT; path=/; Domain=api.hackerone.com; HttpOnly; SameSite=Lax; Secure 36 | X-Request-Id: 37 | - bc58a27f-3140-4457-b7b0-12a3c86f4f03 38 | Etag: 39 | - W/"30c2790e73edc770a9ed3fea69f0e8ea" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | X-Frame-Options: 45 | - DENY 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Xss-Protection: 49 | - 1; mode=block 50 | X-Download-Options: 51 | - noopen 52 | X-Permitted-Cross-Domain-Policies: 53 | - none 54 | Referrer-Policy: 55 | - strict-origin-when-cross-origin 56 | Expect-Ct: 57 | - enforce, max-age=86400 58 | Content-Security-Policy: 59 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 60 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 61 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 62 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 63 | profile-photos.hackerone-user-content.com hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 64 | media-src ''self'' hackerone-us-west-2-production-attachments.s3.us-west-2.amazonaws.com; 65 | script-src ''self'' www.google-analytics.com; style-src ''self'' ''unsafe-inline''; 66 | report-uri https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 67 | Cf-Cache-Status: 68 | - DYNAMIC 69 | Server: 70 | - cloudflare 71 | Cf-Ray: 72 | - 578b7d8e9faa3856-ATL 73 | body: 74 | encoding: ASCII-8BIT 75 | string: '{"data":{"id":"668494","type":"severity","attributes":{"rating":"high","author_type":"Team","user_id":983615,"created_at":"2020-03-23T22:11:50.360Z"}}}' 76 | http_version: null 77 | recorded_at: Mon, 23 Mar 2020 22:11:50 GMT 78 | recorded_with: VCR 5.1.0 79 | -------------------------------------------------------------------------------- /fixtures/vcr_cassettes/user_find_fransrosen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api.hackerone.com/v1/users/fransrosen 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Basic NOPE 12 | User-Agent: 13 | - Faraday v0.13.1 14 | Content-Type: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Thu, 02 Nov 2017 23:24:03 GMT 27 | Content-Type: 28 | - application/json; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - __cfduid=dd872fd23fa7ae9529e3bb8b35adcf44d1509665042; expires=Fri, 02-Nov-18 35 | 23:24:02 GMT; path=/; Domain=api.hackerone.com; HttpOnly; Secure 36 | X-Request-Id: 37 | - eb927f4f-43e8-4232-8bb9-b2651c88a0ca 38 | Etag: 39 | - W/"3894f47342d0f403fb014db76fe89448" 40 | Cache-Control: 41 | - max-age=0, private, must-revalidate 42 | Strict-Transport-Security: 43 | - max-age=31536000; includeSubDomains; preload 44 | Content-Security-Policy: 45 | - 'default-src ''none''; base-uri ''self''; block-all-mixed-content; child-src 46 | www.youtube-nocookie.com; connect-src ''self'' www.google-analytics.com errors.hackerone.net; 47 | font-src ''self''; form-action ''self''; frame-ancestors ''none''; img-src 48 | ''self'' data: cover-photos.hackerone-user-content.com hackathon-photos.hackerone-user-content.com 49 | profile-photos.hackerone-user-content.com hackerone-attachments.s3.amazonaws.com; 50 | media-src ''self'' hackerone-attachments.s3.amazonaws.com; script-src ''self'' 51 | www.google-analytics.com; style-src ''self'' ''unsafe-inline''; report-uri 52 | https://errors.hackerone.net/api/30/csp-report/?sentry_key=61c1e2f50d21487c97a071737701f598' 53 | Referrer-Policy: 54 | - strict-origin-when-cross-origin 55 | X-Content-Type-Options: 56 | - nosniff 57 | X-Download-Options: 58 | - noopen 59 | X-Frame-Options: 60 | - DENY 61 | X-Permitted-Cross-Domain-Policies: 62 | - none 63 | X-Xss-Protection: 64 | - 1; mode=block 65 | Server: 66 | - cloudflare-nginx 67 | Cf-Ray: 68 | - 3b7ad855990492ac-SJC 69 | body: 70 | encoding: ASCII-8BIT 71 | string: !binary |- 72 | eyJkYXRhIjp7ImF0dHJpYnV0ZXMiOnsic2lnbmFsIjo2LjQ3NzgyMjU4MDY0 73 | NTE2LCJpbXBhY3QiOjIyLjYyNjk1MzEyNSwicmVwdXRhdGlvbiI6MTUwMzMs 74 | InVzZXJuYW1lIjoiZnJhbnNyb3NlbiIsIm5hbWUiOiJGcmFucyBSb3PDqW4i 75 | LCJkaXNhYmxlZCI6ZmFsc2UsImNyZWF0ZWRfYXQiOiIyMDEzLTExLTIwVDIx 76 | OjE1OjI5Ljc3MVoiLCJwcm9maWxlX3BpY3R1cmUiOnsiNjJ4NjIiOiJodHRw 77 | czovL3Byb2ZpbGUtcGhvdG9zLmhhY2tlcm9uZS11c2VyLWNvbnRlbnQuY29t 78 | L3Byb2R1Y3Rpb24vMDAwLzAwMS82MzQvNmUwMjI0MmI1ZDFkODkzY2VmNjk4 79 | MTQ0ZmFhNzA2ZmQxZDc1ZmVhMF9zbWFsbC5qcGc/MTM5NjQ2ODU3NiIsIjgy 80 | eDgyIjoiaHR0cHM6Ly9wcm9maWxlLXBob3Rvcy5oYWNrZXJvbmUtdXNlci1j 81 | b250ZW50LmNvbS9wcm9kdWN0aW9uLzAwMC8wMDEvNjM0LzU2NTI4NzgwZGRk 82 | ZTY3ZGVjMmU2ZjlkMTIxMjg3OWFiMTZiYzhmNGFfbWVkaXVtLmpwZz8xMzk2 83 | NDY4NTc2IiwiMTEweDExMCI6Imh0dHBzOi8vcHJvZmlsZS1waG90b3MuaGFj 84 | a2Vyb25lLXVzZXItY29udGVudC5jb20vcHJvZHVjdGlvbi8wMDAvMDAxLzYz 85 | NC80MDQ1YjY2NmQyYWRiMDZjYzc0ZmJhZWQ2NTViN2UzZjM5YjM0Yjc1X2xh 86 | cmdlLmpwZz8xMzk2NDY4NTc2IiwiMjYweDI2MCI6Imh0dHBzOi8vcHJvZmls 87 | ZS1waG90b3MuaGFja2Vyb25lLXVzZXItY29udGVudC5jb20vcHJvZHVjdGlv 88 | bi8wMDAvMDAxLzYzNC9hYmU2YjFlYzJkN2I4YjcxNmVjYmM1M2FjMDkyMDE1 89 | MTM4Y2Y5YmY3X3h0cmFsYXJnZS5qcGc/MTM5NjQ2ODU3NiJ9fSwiaWQiOiIx 90 | NjM0IiwidHlwZSI6InVzZXIifX0= 91 | http_version: 92 | recorded_at: Thu, 02 Nov 2017 23:24:02 GMT 93 | recorded_with: VCR 3.0.3 94 | -------------------------------------------------------------------------------- /hackerone-client.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # frozen_string_literal: true 3 | 4 | lib = File.expand_path("../lib", __FILE__) 5 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 6 | require "hackerone/client/version" 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = "hackerone-client" 10 | spec.version = Hackerone::Client::VERSION 11 | spec.authors = ["Neil Matatall"] 12 | spec.email = ["neil.matatall@gmail.com"] 13 | 14 | spec.summary = %q{A limited client for the HackerOne API} 15 | spec.homepage = "https://github.com/github/hackerone-client" 16 | spec.license = "MIT" 17 | spec.required_ruby_version = ">= 2.6.0" 18 | 19 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 20 | f.match(%r{^(test|spec|features|.github|)/}) 21 | end 22 | spec.bindir = "exe" 23 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 24 | spec.require_paths = ["lib"] 25 | 26 | spec.add_development_dependency "bundler" 27 | spec.add_development_dependency "rake" 28 | spec.add_development_dependency "rspec" 29 | spec.add_development_dependency "vcr" 30 | spec.add_development_dependency "webmock" 31 | spec.add_runtime_dependency "faraday", ">= 2.0.0" 32 | spec.add_runtime_dependency "activesupport" 33 | end 34 | -------------------------------------------------------------------------------- /lib/hackerone/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "faraday" 4 | require "json" 5 | require "active_support" 6 | require "active_support/core_ext/numeric/time" 7 | require "ostruct" 8 | require_relative "client/version" 9 | require_relative "client/report" 10 | require_relative "client/activity" 11 | require_relative "client/program" 12 | require_relative "client/organization" 13 | require_relative "client/asset" 14 | require_relative "client/reporter" 15 | require_relative "client/member" 16 | require_relative "client/user" 17 | require_relative "client/group" 18 | require_relative "client/structured_scope" 19 | require_relative "client/swag" 20 | require_relative "client/address" 21 | require_relative "client/attachment" 22 | require_relative "client/bounty" 23 | require_relative "client/incremental/activities" 24 | require_relative "client/billing_balance" 25 | require "active_support/core_ext/hash" 26 | 27 | module HackerOne 28 | module Client 29 | class NotConfiguredError < StandardError; end 30 | 31 | DEFAULT_LOW_RANGE = 1...999 32 | DEFAULT_MEDIUM_RANGE = 1000...2499 33 | DEFAULT_HIGH_RANGE = 2500...4999 34 | DEFAULT_CRITICAL_RANGE = 5000...100_000_000 35 | 36 | LENIENT_MODE_ENV_VARIABLE = "HACKERONE_CLIENT_LENIENT_MODE" 37 | 38 | REPORT_STATES = %w( 39 | new 40 | triaged 41 | needs-more-info 42 | resolved 43 | not-applicable 44 | informative 45 | duplicate 46 | spam 47 | ) 48 | 49 | class << self 50 | ATTRS = [:low_range, :medium_range, :high_range, :critical_range].freeze 51 | attr_accessor :program 52 | attr_reader *ATTRS 53 | 54 | ATTRS.each do |attr| 55 | define_method "#{attr}=" do |value| 56 | raise ArgumentError, "value must be a range object" unless value.is_a?(Range) 57 | instance_variable_set :"@#{attr}", value 58 | end 59 | end 60 | end 61 | 62 | class Api 63 | def initialize(program = nil) 64 | @program = program 65 | end 66 | 67 | def program 68 | @program || HackerOne::Client.program 69 | end 70 | 71 | def reporters 72 | raise ArgumentError, "Program cannot be nil" unless program 73 | response = self.class.hackerone_api_connection.get do |req| 74 | req.url "programs/#{Program.find(program).id}/reporters" 75 | end 76 | 77 | data = self.class.parse_response(response) 78 | if data.nil? 79 | raise RuntimeError, "Expected data attribute in response: #{response.body}" 80 | end 81 | 82 | data.map do |reporter| 83 | Reporter.new(reporter) 84 | end 85 | end 86 | 87 | ## Returns all reports in a given state, optionally with a time bound 88 | # 89 | # program: the HackerOne program to search on (configure globally with Hackerone::Client.program=) 90 | # since (optional): a time bound, don't include reports earlier than +since+. Must be a DateTime object. 91 | # before (optional): a time bound, don't include reports later than +before+. Must be a DateTime object. 92 | # state (optional): state that a report is in, by default new 93 | # 94 | # returns all open reports or an empty array 95 | def reports(since: 3.days.ago, before: nil, state: :new) 96 | raise ArgumentError, "Program cannot be nil" unless program 97 | raise ArgumentError, "State is invalid" unless REPORT_STATES.include?(state.to_s) 98 | 99 | response = self.class.hackerone_api_connection.get do |req| 100 | options = { 101 | "filter[state][]" => state, 102 | "filter[program][]" => program 103 | } 104 | unless since.nil? 105 | options["filter[created_at__gt]"] = since.iso8601 106 | end 107 | unless before.nil? 108 | options["filter[created_at__lt]"] = before.iso8601 109 | end 110 | 111 | req.url "reports", options 112 | end 113 | 114 | data = self.class.parse_response(response) 115 | 116 | data.map do |report| 117 | Report.new(report) 118 | end 119 | end 120 | 121 | ## Public: create a new report 122 | # 123 | # title: The title of the report 124 | # summary: Summary of the report 125 | # impact: Impact of the report 126 | # severity_rating: severity of report, must be one of https://api.hackerone.com/reference/#severity-ratings 127 | # source: where the report came from, i.e. API, Bugcrowd, etc. 128 | # 129 | # returns an HackerOne::Client::Report object or raises an error if 130 | # error during creation 131 | def create_report(title:, summary:, impact:, severity_rating:, source:) 132 | raise ArgumentError, "Program cannot be nil" unless program 133 | 134 | data = { 135 | "data": { 136 | "type": "report", 137 | "attributes": { 138 | "team_handle": program, 139 | "title": title, 140 | "vulnerability_information": summary, 141 | "impact": impact, 142 | "severity_rating": severity_rating, 143 | "source": source 144 | } 145 | } 146 | } 147 | Report.new(post("reports", data)) 148 | end 149 | 150 | ## Public: retrieve a report 151 | # 152 | # id: the ID of a specific report 153 | # 154 | # returns an HackerOne::Client::Report object or raises an error if 155 | # no report is found. 156 | def report(id) 157 | Report.new(get("reports/#{id}")) 158 | end 159 | 160 | private 161 | def post(endpoint, body) 162 | response = with_retry do 163 | self.class.hackerone_api_connection.post do |req| 164 | req.headers["Content-Type"] = "application/json" 165 | req.body = body.to_json 166 | req.url endpoint 167 | end 168 | end 169 | 170 | self.class.parse_response(response) 171 | end 172 | 173 | def get(endpoint, params = nil) 174 | response = with_retry do 175 | self.class.hackerone_api_connection.get do |req| 176 | req.headers["Content-Type"] = "application/json" 177 | req.params = params || {} 178 | req.url endpoint 179 | end 180 | end 181 | 182 | self.class.parse_response(response) 183 | end 184 | 185 | def self.parse_response(response, extract_data: true) 186 | if response.status.to_s.start_with?("4") 187 | raise ArgumentError, "API called failed, probably your fault: #{response.body}" 188 | elsif response.status.to_s.start_with?("5") 189 | raise RuntimeError, "API called failed, probably their fault: #{response.body}" 190 | elsif response.success? 191 | response_body_json = JSON.parse(response.body, symbolize_names: true) 192 | if extract_data && response_body_json.key?(:data) 193 | response_body_json[:data] 194 | else 195 | response_body_json 196 | end 197 | else 198 | raise RuntimeError, "Not sure what to do here: #{response.body}" 199 | end 200 | end 201 | 202 | def self.hackerone_api_connection 203 | unless ENV["HACKERONE_TOKEN_NAME"] && ENV["HACKERONE_TOKEN"] 204 | raise NotConfiguredError, "HACKERONE_TOKEN_NAME HACKERONE_TOKEN environment variables must be set" 205 | end 206 | 207 | @connection ||= Faraday.new(url: "https://api.hackerone.com/v1") do |faraday| 208 | faraday.request(:authorization, :basic, ENV["HACKERONE_TOKEN_NAME"], ENV["HACKERONE_TOKEN"]) 209 | faraday.adapter Faraday.default_adapter 210 | end 211 | end 212 | 213 | def with_retry(attempts = 3, &block) 214 | attempts_remaining = attempts 215 | 216 | begin 217 | yield 218 | rescue StandardError 219 | if attempts_remaining > 0 220 | attempts_remaining -= 1 221 | sleep (attempts - attempts_remaining) 222 | retry 223 | else 224 | raise 225 | end 226 | end 227 | end 228 | end 229 | end 230 | end 231 | -------------------------------------------------------------------------------- /lib/hackerone/client/activity.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | module Activities 6 | class Activity 7 | delegate :message, :created_at, :updated_at, to: :attributes 8 | delegate :actor, to: :relationships 9 | 10 | def initialize(activity) 11 | @activity = OpenStruct.new activity 12 | end 13 | 14 | def internal? 15 | attributes.internal 16 | end 17 | 18 | def attachments 19 | @attachments ||= activity.relationships.fetch(:attachments, {}) 20 | .fetch(:data, []) 21 | .map { |attachment| HackerOne::Client::Attachment.new(attachment) } 22 | end 23 | 24 | private 25 | 26 | def relationships 27 | OpenStruct.new(activity.relationships) 28 | end 29 | 30 | def attributes 31 | OpenStruct.new(activity.attributes) 32 | end 33 | 34 | attr_reader :activity 35 | end 36 | 37 | class BountyAwarded < Activity 38 | def bounty_amount 39 | formatted_bounty_amount = attributes.bounty_amount || "0" 40 | if ENV[HackerOne::Client::LENIENT_MODE_ENV_VARIABLE] 41 | Float(formatted_bounty_amount) rescue 0 42 | else 43 | begin 44 | Float(formatted_bounty_amount) 45 | rescue ArgumentError 46 | raise ArgumentError.new("Improperly formatted bounty amount") 47 | end 48 | end 49 | end 50 | 51 | def bonus_amount 52 | formatted_bonus_amount = attributes.bonus_amount || "0" 53 | if ENV[HackerOne::Client::LENIENT_MODE_ENV_VARIABLE] 54 | Float(formatted_bonus_amount) rescue 0 55 | else 56 | begin 57 | Float(formatted_bonus_amount) 58 | rescue ArgumentError 59 | raise ArgumentError.new("Improperly formatted bonus amount") 60 | end 61 | end 62 | end 63 | end 64 | 65 | class SwagAwarded < Activity 66 | delegate :swag, to: :relationships 67 | end 68 | 69 | class UserAssignedToBug < Activity 70 | delegate :assigned_user, to: :relationships 71 | end 72 | 73 | class GroupAssignedToBug < Activity 74 | def group 75 | HackerOne::Client::Group.new(relationships.group[:data]) 76 | end 77 | end 78 | 79 | class BugTriaged < Activity 80 | end 81 | 82 | class ReferenceIdAdded < Activity 83 | delegate :reference, :reference_url, to: :attributes 84 | end 85 | 86 | class CommentAdded < Activity 87 | delegate :message, :internal, to: :attributes 88 | end 89 | 90 | class BountySuggested < Activity 91 | delegate :message, :bounty_amount, :bonus_amount, to: :attributes 92 | end 93 | 94 | class ReportLocked < Activity 95 | end 96 | 97 | ACTIVITY_TYPE_CLASS_MAPPING = { 98 | "activity-bounty-awarded" => BountyAwarded, 99 | "activity-swag-awarded" => SwagAwarded, 100 | "activity-user-assigned-to-bug" => UserAssignedToBug, 101 | "activity-group-assigned-to-bug" => GroupAssignedToBug, 102 | "activity-bug-triaged" => BugTriaged, 103 | "activity-reference-id-added" => ReferenceIdAdded, 104 | "activity-comment" => CommentAdded, 105 | "activity-bounty-suggested" => BountySuggested, 106 | "activity-comments-closed" => ReportLocked 107 | }.freeze 108 | 109 | def self.build(activity_data) 110 | activity_type_class = ACTIVITY_TYPE_CLASS_MAPPING.fetch \ 111 | activity_data[:type], Activity 112 | 113 | activity_type_class.new activity_data 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /lib/hackerone/client/address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Address 6 | delegate :name, :street, :city, :postal_code, :state, :country, \ 7 | :created_at, :tshirt_size, :phone_number, to: :attributes 8 | 9 | def initialize(address) 10 | @address = address 11 | end 12 | 13 | def id 14 | @address[:id] 15 | end 16 | 17 | private 18 | 19 | def attributes 20 | OpenStruct.new(@address[:attributes]) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/hackerone/client/asset.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Asset 6 | include ResourceHelper 7 | 8 | DELEGATES = [ 9 | :asset_type, 10 | :identifier, 11 | :description, 12 | :coverage, 13 | :max_severity, 14 | :confidentiality_requirement, 15 | :integrity_requirement, 16 | :availability_requirement, 17 | :created_at, 18 | :updated_at, 19 | :archived_at, 20 | :reference, 21 | :state, 22 | ] 23 | 24 | delegate *DELEGATES, to: :attributes 25 | 26 | attr_reader :organization 27 | 28 | def initialize(asset, organization) 29 | @asset = asset 30 | @organization = organization 31 | end 32 | 33 | def id 34 | @asset[:id] 35 | end 36 | 37 | def update(attributes:) 38 | body = { 39 | type: "asset", 40 | attributes: attributes 41 | } 42 | make_put_request("organizations/#{organization.id}/assets/#{id}", request_body: body) 43 | end 44 | 45 | def programs 46 | relationships.programs[:data].map { |p| Program.new(p) } 47 | end 48 | 49 | private 50 | 51 | def relationships 52 | OpenStruct.new(@asset[:relationships]) 53 | end 54 | 55 | def attributes 56 | OpenStruct.new(@asset[:attributes]) 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/hackerone/client/attachment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Attachment 6 | delegate :expiring_url, :file_name, :content_type, :created_at, \ 7 | :file_size, to: :attributes 8 | 9 | def initialize(attachment) 10 | @attachment = attachment 11 | end 12 | 13 | def id 14 | @attachment[:id] 15 | end 16 | 17 | private 18 | 19 | def attributes 20 | OpenStruct.new(@attachment[:attributes]) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/hackerone/client/billing_balance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class BillingBalance 6 | delegate :balance, to: :attributes 7 | 8 | def initialize(billing_balance) 9 | @billing_balance = OpenStruct.new billing_balance 10 | end 11 | 12 | private 13 | def attributes 14 | OpenStruct.new(@billing_balance[:attributes]) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/hackerone/client/bounty.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Bounty 6 | delegate( 7 | :amount, 8 | :bonus_amount, 9 | :awarded_amount, 10 | :awarded_bonus_amount, 11 | :awarded_currency, 12 | :created_at, 13 | to: :attributes 14 | ) 15 | 16 | def initialize(bounty) 17 | @bounty = bounty 18 | end 19 | 20 | def id 21 | @bounty[:id] 22 | end 23 | 24 | private 25 | 26 | def attributes 27 | OpenStruct.new(@bounty[:attributes]) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/hackerone/client/group.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Group 6 | delegate :name, :permissions, to: :attributes 7 | 8 | def initialize(group) 9 | @group = group 10 | end 11 | 12 | def id 13 | @group[:id] 14 | end 15 | 16 | private 17 | 18 | def attributes 19 | OpenStruct.new(@group[:attributes]) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/hackerone/client/incremental/activities.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | module Incremental 6 | class Activities 7 | include ResourceHelper 8 | 9 | attr_reader :program, :updated_at_after, :page_size 10 | 11 | def initialize(program, updated_at_after: nil, page_size: 25) 12 | @program = program 13 | @updated_at_after = updated_at_after 14 | @page_size = page_size 15 | end 16 | 17 | def traverse 18 | loop do 19 | activities.each do |activity| 20 | yield activity 21 | end 22 | 23 | break if next_page.nil? 24 | end 25 | end 26 | 27 | def activities 28 | @activities ||= current_page[:data].map do |activity_json| 29 | HackerOne::Client::Activities.build activity_json 30 | end 31 | end 32 | 33 | def next_page 34 | return nil unless next_cursor.present? 35 | 36 | # Set cursor to next page 37 | @updated_at_after = next_cursor 38 | 39 | # Remove memoization 40 | @current_page = nil 41 | @activities = nil 42 | 43 | # Fetch new page 44 | current_page 45 | 46 | activities 47 | end 48 | 49 | private 50 | 51 | def current_page 52 | @current_page ||= make_get_request( 53 | "incremental/activities", 54 | extract_data: false, 55 | params: { 56 | handle: program.handle, 57 | first: page_size, 58 | updated_at_after: updated_at_after 59 | } 60 | ) 61 | end 62 | 63 | def next_cursor 64 | current_page[:meta][:max_updated_at] 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/hackerone/client/member.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Member 6 | delegate :permissions, to: :attributes 7 | 8 | def initialize(member) 9 | @member = member 10 | end 11 | 12 | def user 13 | @user ||= User.new(relationships.user[:data]) 14 | end 15 | 16 | def id 17 | @member[:id] 18 | end 19 | 20 | private 21 | 22 | def attributes 23 | OpenStruct.new(@member[:attributes]) 24 | end 25 | 26 | def relationships 27 | OpenStruct.new(@member[:relationships]) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/hackerone/client/organization.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Organization 6 | include ResourceHelper 7 | 8 | delegate :handle, :created_at, :updated_at, to: :attributes 9 | 10 | def initialize(org) 11 | @organization = org 12 | end 13 | 14 | def id 15 | @organization[:id] 16 | end 17 | 18 | def assets(page_number: 1, page_size: 100) 19 | make_get_request( 20 | "organizations/#{id}/assets", 21 | params: { page: { number: page_number, size: page_size } } 22 | ).map do |asset_data| 23 | Asset.new(asset_data, self) 24 | end 25 | end 26 | 27 | private 28 | 29 | def attributes 30 | OpenStruct.new(@organization[:attributes]) 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/hackerone/client/program.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "./resource_helper" 4 | 5 | module HackerOne 6 | module Client 7 | class Program 8 | include ResourceHelper 9 | 10 | delegate :handle, to: :attributes 11 | 12 | def self.find(program_handle_we_want) 13 | my_programs.find do |program| 14 | program.handle == program_handle_we_want 15 | end 16 | end 17 | 18 | def initialize(program) 19 | @program = program 20 | end 21 | 22 | def id 23 | @program[:id] 24 | end 25 | 26 | def incremental_activities(updated_at_after: nil, page_size: 25) 27 | HackerOne::Client::Incremental::Activities.new( 28 | self, 29 | updated_at_after: updated_at_after, 30 | page_size: page_size 31 | ) 32 | end 33 | 34 | def attributes 35 | OpenStruct.new(@program[:attributes]) 36 | end 37 | 38 | def member?(username) 39 | find_member(username).present? 40 | end 41 | 42 | def group?(groupname) 43 | find_group(groupname).present? 44 | end 45 | 46 | def find_member(username) 47 | members.find { |member| member.user.username == username } 48 | end 49 | 50 | def find_group(groupname) 51 | groups.find { |group| group.name == groupname } 52 | end 53 | 54 | def structured_scopes(page_number: 1, page_size: 100) 55 | make_get_request( 56 | "programs/#{id}/structured_scopes", 57 | params: { page: { number: page_number, size: page_size } } 58 | ).map do |data| 59 | StructuredScope.new(data, self) 60 | end 61 | end 62 | 63 | def update_policy(policy:) 64 | body = { 65 | type: "program-policy", 66 | attributes: { 67 | policy: policy 68 | } 69 | } 70 | make_put_request("programs/#{id}/policy", request_body: body) 71 | end 72 | 73 | def common_responses(page_number: 1, page_size: 100) 74 | make_get_request( 75 | "programs/#{id}/common_responses", 76 | params: { page: { number: page_number, size: page_size } } 77 | ) 78 | end 79 | 80 | def swag(page_number: 1, page_size: 100) 81 | response_body = make_get_request( 82 | "programs/#{id}/swag", 83 | params: { page: { number: page_number, size: page_size } } 84 | ) 85 | response_body.map { |r| Swag.new(r, self) } 86 | end 87 | 88 | def balance 89 | response_body = make_get_request( 90 | "programs/#{id}/billing/balance" 91 | ) 92 | BillingBalance.new(response_body).balance 93 | end 94 | 95 | def members 96 | @members ||= relationships.members[:data].map { |member_data| Member.new(member_data) } 97 | end 98 | 99 | def groups 100 | @groups ||= relationships.groups[:data].map { |group_data| Group.new(group_data) } 101 | end 102 | 103 | def organization 104 | @organization ||= Organization.new(relationships.organization[:data]) 105 | end 106 | 107 | private 108 | 109 | def relationships 110 | # Relationships are only included in the /programs/:id call, 111 | # which is why we need to do a separate call here. 112 | @relationships ||= begin 113 | response = HackerOne::Client::Api.hackerone_api_connection.get do |req| 114 | req.url "programs/#{id}" 115 | end 116 | 117 | data = HackerOne::Client::Api.parse_response(response) 118 | OpenStruct.new(data[:relationships]) 119 | end 120 | end 121 | 122 | def self.my_programs 123 | @my_programs ||= begin 124 | response = HackerOne::Client::Api.hackerone_api_connection.get do |req| 125 | req.url "me/programs" 126 | end 127 | 128 | data = HackerOne::Client::Api.parse_response(response) 129 | data.map { |program| self.new(program) } 130 | end 131 | end 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/hackerone/client/report.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "./resource_helper" 4 | require_relative "./weakness" 5 | require_relative "./activity" 6 | 7 | module HackerOne 8 | module Client 9 | class Report 10 | include ResourceHelper 11 | 12 | STATES = %w( 13 | new 14 | triaged 15 | needs-more-info 16 | resolved 17 | not-applicable 18 | informative 19 | duplicate 20 | spam 21 | ).map(&:to_sym).freeze 22 | 23 | STATES_REQUIRING_STATE_CHANGE_MESSAGE = %w( 24 | needs-more-info 25 | informative 26 | duplicate 27 | ).map(&:to_sym).freeze 28 | 29 | RESOLVED_STATES = %w( 30 | resolved 31 | not-applicable 32 | informative 33 | duplicate 34 | spam 35 | ).map(&:to_sym).freeze 36 | 37 | SEVERITY_RATINGS = %w( 38 | none 39 | low 40 | medium 41 | high 42 | critical 43 | ).freeze 44 | 45 | class << self 46 | def add_on_state_change_hook(proc) 47 | on_state_change_hooks << proc 48 | end 49 | 50 | def clear_on_state_change_hooks 51 | @on_state_change_hooks = [] 52 | end 53 | 54 | def on_state_change_hooks 55 | @on_state_change_hooks ||= [] 56 | end 57 | end 58 | 59 | def initialize(report) 60 | @report = report 61 | end 62 | 63 | def id 64 | @report[:id] 65 | end 66 | 67 | def title 68 | attributes[:title] 69 | end 70 | 71 | def created_at 72 | attributes[:created_at] 73 | end 74 | 75 | def issue_tracker_reference_url 76 | attributes[:issue_tracker_reference_url] 77 | end 78 | 79 | def issue_tracker_reference_id 80 | attributes[:issue_tracker_reference_id] 81 | end 82 | 83 | def severity 84 | attributes[:severity] 85 | end 86 | 87 | def state 88 | attributes[:state] 89 | end 90 | 91 | def reporter 92 | relationships 93 | .fetch(:reporter, {}) 94 | .fetch(:data, {}) 95 | .fetch(:attributes, {}) 96 | end 97 | 98 | def assignee 99 | if assignee_relationship = relationships[:assignee] 100 | HackerOne::Client::User.new(assignee_relationship[:data]) 101 | else 102 | nil 103 | end 104 | end 105 | 106 | def payment_total 107 | payments.reduce(0) { |total, payment| total + payment_amount(payment) } 108 | end 109 | 110 | def structured_scope 111 | StructuredScope.new(relationships[:structured_scope].fetch(:data, {}), program) 112 | end 113 | 114 | # Excludes reports where the payout amount is 0 indicating swag-only or no 115 | # payout for the issue supplied 116 | def risk 117 | case payment_total 118 | when HackerOne::Client.low_range || DEFAULT_LOW_RANGE 119 | "low" 120 | when HackerOne::Client.medium_range || DEFAULT_MEDIUM_RANGE 121 | "medium" 122 | when HackerOne::Client.high_range || DEFAULT_HIGH_RANGE 123 | "high" 124 | when HackerOne::Client.critical_range || DEFAULT_CRITICAL_RANGE 125 | "critical" 126 | end 127 | end 128 | 129 | def summary 130 | attributes[:vulnerability_information] 131 | end 132 | 133 | def weakness 134 | @weakness ||= Weakness.new(relationships.fetch(:weakness, {}).fetch(:data, {}).fetch(:attributes, {})) 135 | end 136 | 137 | def classification_label 138 | weakness.to_owasp 139 | end 140 | 141 | # Bounty writeups just use the key, and not the label value. 142 | def writeup_classification 143 | classification_label.split("-").first 144 | end 145 | 146 | def attachments 147 | @attachments ||= relationships.fetch(:attachments, {}) 148 | .fetch(:data, []) 149 | .map { |attachment| HackerOne::Client::Attachment.new(attachment) } 150 | end 151 | 152 | def activities 153 | if ships = relationships.fetch(:activities, {}).fetch(:data, []) 154 | ships.map do |activity_data| 155 | Activities.build(activity_data) 156 | end 157 | end 158 | end 159 | 160 | def program 161 | @program || Program.find(relationships[:program][:data][:attributes][:handle]) 162 | end 163 | 164 | def award_bounty(message:, amount:, bonus_amount: nil) 165 | request_body = { 166 | message: message, 167 | amount: amount, 168 | bonus_amount: bonus_amount 169 | } 170 | 171 | response_body = make_post_request( 172 | "reports/#{id}/bounties", 173 | request_body: request_body 174 | ) 175 | Bounty.new(response_body) 176 | end 177 | 178 | def award_swag(message:) 179 | request_body = { 180 | message: message 181 | } 182 | 183 | response_body = make_post_request( 184 | "reports/#{id}/swags", 185 | request_body: request_body 186 | ) 187 | Swag.new(response_body, program) 188 | end 189 | 190 | def update_severity(rating:) 191 | raise ArgumentError, "Invalid severity rating" unless SEVERITY_RATINGS.include?(rating.to_s) 192 | 193 | request_body = { 194 | type: "severity", 195 | attributes: { 196 | rating: rating 197 | } 198 | } 199 | response_body = make_post_request( 200 | "reports/#{id}/severities", 201 | request_body: request_body 202 | ) 203 | @report[:attributes][:severity] = { rating: rating } 204 | Activities.build(response_body) 205 | end 206 | 207 | def suggest_bounty(message:, amount:, bonus_amount: nil) 208 | request_body = { 209 | message: message, 210 | amount: amount, 211 | bonus_amount: bonus_amount 212 | } 213 | 214 | response_body = make_post_request( 215 | "reports/#{id}/bounty_suggestions", 216 | request_body: request_body 217 | ) 218 | Activities.build(response_body) 219 | end 220 | 221 | ## Idempotent: change the state of a report. See STATES for valid values. 222 | # 223 | # id: the ID of the report 224 | # state: the state in which the report is to be put in 225 | # 226 | # returns an HackerOne::Client::Report object or raises an error if 227 | # no report is found. 228 | def state_change(state, message = nil, attributes = {}) 229 | raise ArgumentError, "state (#{state}) must be one of #{STATES}" unless STATES.include?(state) 230 | 231 | old_state = self.state 232 | body = { 233 | type: "state-change", 234 | attributes: { 235 | state: state 236 | } 237 | } 238 | 239 | body[:attributes] = body[:attributes].reverse_merge(attributes) 240 | 241 | if message 242 | body[:attributes][:message] = message 243 | elsif STATES_REQUIRING_STATE_CHANGE_MESSAGE.include?(state) 244 | fail ArgumentError, "State #{state} requires a message. No message was supplied." 245 | else 246 | # message is in theory optional, but a value appears to be required. 247 | body[:attributes][:message] = "" 248 | end 249 | response_json = make_post_request("reports/#{id}/state_changes", request_body: body) 250 | @report = response_json 251 | self.class.on_state_change_hooks.each do |hook| 252 | hook.call(self, old_state.to_s, state.to_s) 253 | end 254 | self 255 | end 256 | 257 | ## Idempotent: Add a report reference to a project 258 | # 259 | # id: the ID of the report 260 | # state: value for the reference (e.g. issue number or relative path to cross-repo issue) 261 | # 262 | # returns an HackerOne::Client::Report object or raises an error if 263 | # no report is found. 264 | def add_report_reference(reference) 265 | body = { 266 | type: "issue-tracker-reference-id", 267 | attributes: { 268 | reference: reference 269 | } 270 | } 271 | 272 | response_json = make_post_request("reports/#{id}/issue_tracker_reference_id", request_body: body) 273 | @report = response_json[:relationships][:report][:data] 274 | self 275 | end 276 | 277 | ## Idempotent: add the issue reference and put the report into the "triage" state. 278 | # 279 | # id: the ID of the report 280 | # state: value for the reference (e.g. issue number or relative path to cross-repo issue) 281 | # 282 | # returns an HackerOne::Client::Report object or raises an error if 283 | # no report is found. 284 | def triage(reference) 285 | add_report_reference(reference) 286 | state_change(:triaged) 287 | end 288 | 289 | # Add a comment to a report. By default, internal comments will be added. 290 | # 291 | # id: the ID of the report 292 | # message: the content of the comment that will be created 293 | # internal: "team only" comment (true, default) or "all participants" 294 | def add_comment(message, internal: true) 295 | fail ArgumentError, "message is required" if message.blank? 296 | 297 | body = { 298 | type: "activity-comment", 299 | attributes: { 300 | message: message, 301 | internal: internal 302 | } 303 | } 304 | 305 | response_json = make_post_request("reports/#{id}/activities", request_body: body) 306 | HackerOne::Client::Activities.build(response_json) 307 | end 308 | 309 | def lock! 310 | unless RESOLVED_STATES.include? self.state.to_sym 311 | raise ArgumentError, "Report must be closed before locking" 312 | end 313 | 314 | body = { 315 | type: "activity-comments-closed" 316 | } 317 | 318 | response_json = make_put_request("reports/#{id}/close_comments", request_body: body) 319 | HackerOne::Client::Activities.build(response_json) 320 | end 321 | 322 | def assign_to_user(name) 323 | member = program.find_member(name) 324 | _assign_to(member.user.id, :user) 325 | end 326 | 327 | def assign_to_group(name) 328 | group = program.find_group(name) 329 | _assign_to(group.id, :group) 330 | end 331 | 332 | def unassign 333 | _assign_to(nil, :nobody) 334 | end 335 | 336 | private 337 | 338 | def payments 339 | activities.select { |activity| activity.is_a? Activities::BountyAwarded } 340 | end 341 | 342 | def payment_amount(payment) 343 | payment.bounty_amount 344 | end 345 | 346 | def attributes 347 | @report[:attributes] 348 | end 349 | 350 | def relationships 351 | @report[:relationships] 352 | end 353 | 354 | def vulnerability_types 355 | relationships.fetch(:vulnerability_types, {}).fetch(:data, []) 356 | end 357 | 358 | def _assign_to(assignee_id, assignee_type) 359 | request_body = { 360 | type: assignee_type, 361 | } 362 | request_body[:id] = assignee_id if assignee_id 363 | 364 | response = HackerOne::Client::Api.hackerone_api_connection.put do |req| 365 | req.headers["Content-Type"] = "application/json" 366 | req.url "reports/#{id}/assignee" 367 | req.body = { data: request_body }.to_json 368 | end 369 | unless response.success? 370 | fail("Unable to assign report #{id} to #{assignee_type} with id '#{assignee_id}'. Response status: #{response.status}, body: #{response.body}") 371 | end 372 | 373 | @report = parse_response response 374 | end 375 | end 376 | end 377 | end 378 | -------------------------------------------------------------------------------- /lib/hackerone/client/reporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Reporter 6 | delegate :username, :name, :created_at, to: :attributes 7 | 8 | def initialize(reporter) 9 | @reporter = reporter 10 | end 11 | 12 | def id 13 | @reporter[:id] 14 | end 15 | 16 | def attributes 17 | OpenStruct.new(@reporter[:attributes]) 18 | end 19 | 20 | def disabled? 21 | attributes.disabled 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/hackerone/client/resource_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | module ResourceHelper 6 | def self.included(base) 7 | base.extend(self) 8 | end 9 | 10 | def parse_response(response, extract_data: true) 11 | HackerOne::Client::Api.parse_response( 12 | response, 13 | extract_data: extract_data 14 | ) 15 | end 16 | 17 | def make_put_request(url, request_body:, extract_data: true) 18 | response = HackerOne::Client::Api.hackerone_api_connection.put do |req| 19 | req.headers["Content-Type"] = "application/json" 20 | req.url url 21 | req.body = { data: request_body }.to_json 22 | end 23 | 24 | parse_response(response, extract_data: extract_data) 25 | end 26 | 27 | def make_post_request(url, request_body:, extract_data: true) 28 | response = HackerOne::Client::Api.hackerone_api_connection.post do |req| 29 | req.headers["Content-Type"] = "application/json" 30 | req.url url 31 | req.body = { data: request_body }.to_json 32 | end 33 | 34 | parse_response(response, extract_data: extract_data) 35 | end 36 | 37 | def make_get_request(url, params: {}, extract_data: true) 38 | response = HackerOne::Client::Api.hackerone_api_connection.get do |req| 39 | req.headers["Content-Type"] = "application/json" 40 | req.url url 41 | req.params = params 42 | end 43 | 44 | parse_response(response, extract_data: extract_data) 45 | end 46 | 47 | private 48 | 49 | def api_connection 50 | HackerOne::Client::Api.hackerone_api_connection 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/hackerone/client/structured_scope.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class StructuredScope 6 | include ResourceHelper 7 | 8 | DELEGATES = [ 9 | :asset_identifier, 10 | :asset_type, 11 | :availability_requirement, 12 | :confidentiality_requirement, 13 | :eligible_for_bounty, 14 | :eligible_for_submission, 15 | :instruction, 16 | :integrity_requirement, 17 | :max_severity, 18 | :reference 19 | ] 20 | 21 | delegate *DELEGATES, to: :attributes 22 | 23 | attr_reader :program 24 | 25 | def initialize(scope, program = nil) 26 | @program = program 27 | @scope = scope 28 | end 29 | 30 | def id 31 | @scope[:id] 32 | end 33 | 34 | def update(attributes:) 35 | body = { 36 | type: "structured-scope", 37 | attributes: attributes 38 | } 39 | make_put_request("programs/#{program.id}/structured_scopes/#{id}", request_body: body) 40 | end 41 | 42 | private 43 | 44 | def attributes 45 | OpenStruct.new(@scope[:attributes]) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/hackerone/client/swag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Swag 6 | include ResourceHelper 7 | delegate :sent, :created_at, to: :attributes 8 | 9 | def initialize(swag, program = nil) 10 | @swag = swag 11 | @program = program 12 | end 13 | 14 | def id 15 | @swag[:id] 16 | end 17 | 18 | def sent? 19 | !!attributes.sent 20 | end 21 | 22 | def user 23 | if user_relationship = relationships[:user] 24 | HackerOne::Client::User.new(user_relationship[:data]) 25 | end 26 | end 27 | 28 | def address 29 | if address_relationship = relationships[:address] 30 | HackerOne::Client::Address.new(address_relationship[:data]) 31 | end 32 | end 33 | 34 | def mark_as_sent! 35 | body = { 36 | type: "swag", 37 | attributes: { 38 | sent: true 39 | } 40 | } 41 | 42 | response_json = make_put_request("programs/#{@program.id}/swag/#{id}", request_body: body) 43 | self.class.new(response_json, @program) 44 | end 45 | 46 | private 47 | 48 | def attributes 49 | OpenStruct.new(@swag[:attributes]) 50 | end 51 | 52 | def relationships 53 | @swag[:relationships] 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/hackerone/client/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class User 6 | include ResourceHelper 7 | 8 | delegate :username, :signal, :impact, :reputation, to: :attributes 9 | 10 | def self.find(username_we_want) 11 | user_json = make_get_request("users/#{username_we_want}") 12 | new(user_json) 13 | end 14 | 15 | def initialize(user) 16 | @user = user 17 | end 18 | 19 | def id 20 | @user[:id] 21 | end 22 | 23 | private 24 | 25 | def attributes 26 | OpenStruct.new(@user[:attributes]) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/hackerone/client/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Hackerone 4 | module Client 5 | VERSION = "0.23.0" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/hackerone/client/weakness.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HackerOne 4 | module Client 5 | class Weakness 6 | class << self 7 | def validate_cwe!(cwe) 8 | fail NotAnOwaspWeaknessError if cwe.upcase.start_with?("CAPEC-") 9 | fail StandardError::ArgumentError unless cwe.upcase.start_with?("CWE-") 10 | end 11 | 12 | def extract_cwe_number(cwe) 13 | return if cwe.nil? 14 | validate_cwe!(cwe) 15 | 16 | cwe.split("CWE-").last.to_i 17 | end 18 | end 19 | 20 | class NotAnOwaspWeaknessError < StandardError 21 | def message 22 | "CAPEC labels do not describe OWASP weaknesses" 23 | end 24 | end 25 | 26 | CLASSIFICATION_MAPPING = { 27 | "None Applicable" => "A0-Other", 28 | "Denial of Service" => "A0-Other", 29 | "Memory Corruption" => "A0-Other", 30 | "Cryptographic Issue" => "A0-Other", 31 | "Privilege Escalation" => "A0-Other", 32 | "UI Redressing (Clickjacking)" => "A0-Other", 33 | "Command Injection" => "A1-Injection", 34 | "Remote Code Execution" => "A1-Injection", 35 | "SQL Injection" => "A1-Injection", 36 | "Authentication" => "A2-AuthSession", 37 | "Cross-Site Scripting (XSS)" => "A3-XSS", 38 | "Information Disclosure" => "A6-DataExposure", 39 | "Cross-Site Request Forgery (CSRF)" => "A8-CSRF", 40 | "Unvalidated / Open Redirect" => "A10-Redirects" 41 | } 42 | 43 | OWASP_TOP_10_2013_TO_CWE = { 44 | "A1-Injection" => [77, 78, 88, 89, 90, 91, 564], 45 | "A2-AuthSession" => 46 | [287, 613, 522, 256, 384, 472, 346, 441, 523, 620, 640, 319, 311], 47 | "A3-XSS" => [79], 48 | "A4-DirectObjRef" => [639, 99, 22], 49 | "A5-Misconfig" => [16, 2, 215, 548, 209], 50 | "A6-DataExposure" => [312, 319, 310, 326, 320, 311, 325, 328, 327], 51 | "A7-MissingACL" => [285, 287], 52 | "A8-CSRF" => [352, 642, 613, 346, 441], 53 | "A9-KnownVuln" => [], 54 | "A10-Redirects" => [601], 55 | }.freeze 56 | 57 | OWASP_DEFAULT = "A0-Other".freeze 58 | 59 | def initialize(weakness) 60 | @attributes = weakness 61 | end 62 | 63 | def to_owasp 64 | from_cwe = OWASP_TOP_10_2013_TO_CWE.map do |owasp, cwes| 65 | owasp if cwes.include?(self.class.extract_cwe_number(to_cwe)) 66 | end.compact.first 67 | 68 | from_cwe || CLASSIFICATION_MAPPING[@attributes[:name]] || OWASP_DEFAULT 69 | end 70 | 71 | def to_cwe 72 | @attributes[:external_id] 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/hackerone/client/address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Address do 6 | let (:address_data) do 7 | { 8 | name: "Brian Anglin", 9 | street: " 88 Colin P Kelly Jr Street", 10 | city: "San Francisco", 11 | postal_code: "94107", 12 | state: "California", 13 | country: "United States", 14 | created_at: "2019-10-21T20:50:40.159Z", 15 | tshirt_size: "M_Medium", 16 | phone_number: "555-555-5555" 17 | } 18 | end 19 | 20 | let(:example) do 21 | { 22 | id: "7374", 23 | type: "address", 24 | attributes: address_data, 25 | }.with_indifferent_access 26 | end 27 | 28 | it "creates the address type with attributes" do 29 | address = HackerOne::Client::Address.new example 30 | expect(address.class).to eq described_class 31 | expect(address.id).to eq "7374" 32 | 33 | address_data.keys.each do |key| 34 | expect(address.send key.to_s).to eq address_data[key] 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/hackerone/client/asset_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Asset do 6 | before(:all) do 7 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 8 | ENV["HACKERONE_TOKEN"] = "bar" 9 | end 10 | before(:each) do 11 | stub_request(:get, "https://api.hackerone.com/v1/programs/18969"). 12 | to_return(body: <<~JSON) 13 | { 14 | "data": { 15 | "id": "18969", 16 | "type": "program", 17 | "attributes": { 18 | "handle": "github", 19 | "created_at": "2016-02-02T04:05:06.000Z", 20 | "updated_at": "2016-02-02T04:05:06.000Z" 21 | }, 22 | "relationships": { 23 | "organization": { 24 | "data": { 25 | "id": "14", 26 | "type": "organization", 27 | "attributes": { 28 | "handle": "api-example", 29 | "created_at": "2016-02-02T04:05:06.000Z", 30 | "updated_at": "2016-02-02T04:05:06.000Z" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | JSON 38 | 39 | stub_request(:get, "https://api.hackerone.com/v1/organizations/14/assets?page%5Bnumber%5D=1&page%5Bsize%5D=100"). 40 | to_return(body: <<~JSON2) 41 | { 42 | "data": [ 43 | { 44 | "id": "2", 45 | "type": "asset", 46 | "attributes": { 47 | "asset_type": "domain", 48 | "domain_name": "hackerone.com", 49 | "description": null, 50 | "coverage": "untested", 51 | "max_severity": "critical", 52 | "confidentiality_requirement": "high", 53 | "integrity_requirement": "high", 54 | "availability_requirement": "high", 55 | "created_at": "2016-02-02T04:05:06.000Z", 56 | "updated_at": "2016-02-02T04:05:06.000Z", 57 | "archived_at": "2017-02-02T04:05:06.000Z", 58 | "reference": "reference", 59 | "state": "confirmed" 60 | }, 61 | "relationships": { 62 | "asset_tags": { 63 | "data": [ 64 | { 65 | "id": "1", 66 | "type": "asset-tag", 67 | "attributes": { 68 | "name": "test" 69 | }, 70 | "relationships": { 71 | "asset_tag_category": { 72 | "data": { 73 | "id": "2", 74 | "type": "asset-tag-category", 75 | "attributes": { 76 | "name": "test" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | ] 83 | }, 84 | "programs": { 85 | "data": [ 86 | { 87 | "id": "18969", 88 | "type": "program", 89 | "attributes": { 90 | "handle": "github", 91 | "name": "team name" 92 | } 93 | } 94 | ] 95 | }, 96 | "attachments": { 97 | "data": [ 98 | { 99 | "id": "1337", 100 | "type": "attachment", 101 | "attributes": { 102 | "expiring_url": "https://attachments.s3.amazonaws.com/G74PuDP6qdEdN2rpKNLkVwZF", 103 | "created_at": "2016-02-02T04:05:06.000Z", 104 | "file_name": "example.png", 105 | "content_type": "image/png", 106 | "file_size": 16115 107 | } 108 | } 109 | ] 110 | } 111 | } 112 | } 113 | ], 114 | "links": {} 115 | } 116 | JSON2 117 | end 118 | 119 | after(:each) do 120 | # clear cached programs to prevent contaminatin between tests 121 | HackerOne::Client::Program.instance_variable_set(:@my_programs, nil) 122 | end 123 | 124 | let(:program) do 125 | VCR.use_cassette(:programs) do 126 | HackerOne::Client::Program.find("github") 127 | end 128 | end 129 | 130 | let(:organization) do 131 | program.organization 132 | end 133 | 134 | let(:assets) do 135 | organization.assets 136 | end 137 | 138 | let(:asset) { assets[0] } 139 | 140 | it "returns a collection" do 141 | expect(assets).to be_kind_of(Array) 142 | expect(assets.size).to eq(1) 143 | end 144 | 145 | it "returns id" do 146 | expect(asset.id).to be_present 147 | expect(asset.id).to eq("2") 148 | end 149 | 150 | it "returns organization" do 151 | expect(asset.organization).to be_present 152 | expect(asset.organization.id).to eq("14") 153 | end 154 | 155 | it "returns programs" do 156 | expect(asset.programs).to be_kind_of(Array) 157 | expect(asset.programs.first.id).to eq("18969") 158 | end 159 | 160 | it "updates the asset" do 161 | req = stub_request(:put, "https://api.hackerone.com/v1/organizations/14/assets/2"). 162 | with { |r| 163 | r.body == <<~BODY.strip 164 | {"data":{"type":"asset","attributes":{"description":"This is the new description"}}} 165 | BODY 166 | }. 167 | to_return(body: "{}") # we are not using the response for now so not bothering to stub it properly 168 | 169 | asset.update( 170 | attributes: { 171 | description: "This is the new description" 172 | } 173 | ) 174 | 175 | expect(req).to have_been_requested 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /spec/hackerone/client/attachment_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Attachment do 6 | let (:attachment_data) do 7 | { 8 | expiring_url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", 9 | created_at: "2016-02-02T04:05:06.000Z", 10 | file_name: "root.rb", 11 | content_type: "image/jpeg", 12 | file_size: 2871 13 | } 14 | end 15 | 16 | let(:example) do 17 | { 18 | id: "1337", 19 | type: "attachment", 20 | attributes: attachment_data, 21 | }.with_indifferent_access 22 | end 23 | 24 | it "creates the attachment object with attributes" do 25 | attachment = HackerOne::Client::Attachment.new example 26 | expect(attachment.class).to eq described_class 27 | expect(attachment.id).to eq "1337" 28 | 29 | attachment_data.keys.each do |key| 30 | expect(attachment.send key.to_s).to eq attachment_data[key] 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/hackerone/client/program_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Program do 6 | before(:all) do 7 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 8 | ENV["HACKERONE_TOKEN"] = "bar" 9 | end 10 | 11 | let(:program) do 12 | VCR.use_cassette(:programs) do 13 | described_class.find "github" 14 | end 15 | end 16 | 17 | describe "find" do 18 | it "returns a team as object when provided the handle" do 19 | expect(program.id).to eq("18969") 20 | expect(program.handle).to eq("github") 21 | end 22 | end 23 | 24 | describe "common responses" do 25 | it "returns the common responses of the program" do 26 | expect( 27 | VCR.use_cassette(:common_responses) do 28 | program.common_responses 29 | end 30 | ).to be_present 31 | end 32 | end 33 | 34 | describe "policy" do 35 | it "updates the policy of the program" do 36 | expect( 37 | VCR.use_cassette(:update_policy) do 38 | program.update_policy(policy: "Hello World, updating policy") 39 | end 40 | ).to be_present 41 | end 42 | end 43 | 44 | describe "swag" do 45 | it "returns the pending swag awards for the program" do 46 | expect( 47 | VCR.use_cassette(:swag) do 48 | program.swag 49 | end 50 | ).to be_present 51 | end 52 | end 53 | 54 | describe "structured_scopes" do 55 | it "returns a list of structured scopes" do 56 | stub_request(:get, "https://api.hackerone.com/v1/programs/18969/structured_scopes?page%5Bnumber%5D=1&page%5Bsize%5D=100"). 57 | to_return(body: <<~JSON) 58 | { 59 | "data": [ 60 | { 61 | "id": "57", 62 | "type": "structured-scope", 63 | "attributes": { 64 | "asset_identifier": "api.example.com", 65 | "asset_type": "URL", 66 | "confidentiality_requirement": "high", 67 | "integrity_requirement": "high", 68 | "availability_requirement": "high", 69 | "max_severity": "critical", 70 | "created_at": "2015-02-02T04:05:06.000Z", 71 | "updated_at": "2016-05-02T04:05:06.000Z", 72 | "instruction": null, 73 | "eligible_for_bounty": true, 74 | "eligible_for_submission": true, 75 | "reference": "H001001" 76 | } 77 | } 78 | ], 79 | "links": {} 80 | } 81 | JSON 82 | expect(program.structured_scopes).to be_a(Array) 83 | expect(program.structured_scopes.first).to be_a(HackerOne::Client::StructuredScope) 84 | end 85 | end 86 | 87 | describe ".incremental_activities" do 88 | it "can traverse through the activities of a program" do 89 | incremental_activities = program.incremental_activities(updated_at_after: DateTime.new(2017, 12, 4, 15, 38), page_size: 3) 90 | 91 | activities = [] 92 | VCR.use_cassette(:traverse_through_3_activities) do 93 | incremental_activities.traverse do |activity| 94 | activities << activity 95 | end 96 | end 97 | 98 | expect(activities.size).to eq 3 99 | group_assigned_to_bug, comment_added, bounty_awarded = activities 100 | expect(group_assigned_to_bug) 101 | .to be_a HackerOne::Client::Activities::GroupAssignedToBug 102 | expect(group_assigned_to_bug.group).to be_present 103 | expect(group_assigned_to_bug.group.name).to eq "Standard" 104 | expect(comment_added) 105 | .to be_a HackerOne::Client::Activities::CommentAdded 106 | expect(comment_added.message).to eq "this is a comment" 107 | expect(bounty_awarded) 108 | .to be_a HackerOne::Client::Activities::BountyAwarded 109 | expect(bounty_awarded.message).to eq "Here's a bounty!" 110 | end 111 | 112 | it "can traverse through all activities of a program" do 113 | incremental_activities = program.incremental_activities 114 | 115 | activities = [] 116 | VCR.use_cassette(:traverse_through_all_activities) do 117 | incremental_activities.traverse do |activity| 118 | activities << activity 119 | end 120 | end 121 | 122 | expect(activities.size).to eq 27 123 | 124 | # Assert no activity appears twice 125 | name_and_updated_at = activities.map do |activity| 126 | "#{activity.class} #{activity.updated_at}" 127 | end 128 | expect(name_and_updated_at.size).to eq name_and_updated_at.uniq.size 129 | end 130 | end 131 | 132 | describe "balance" do 133 | it "gets the balance of a program" do 134 | expect( 135 | VCR.use_cassette(:get_balance) do 136 | program.balance 137 | end 138 | ).to eq("118386.40") 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /spec/hackerone/client/report_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Report do 6 | let(:api) { HackerOne::Client::Api.new("github") } 7 | 8 | before(:all) do 9 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 10 | ENV["HACKERONE_TOKEN"] = "bar" 11 | end 12 | 13 | let(:report) do 14 | VCR.use_cassette(:report) do 15 | api.report(200) 16 | end 17 | end 18 | 19 | it "classifies risk" do 20 | begin 21 | expect(report.risk).to eq("low") 22 | HackerOne::Client.low_range = 1..50 23 | HackerOne::Client.medium_range = 50..1000 24 | expect(report.risk).to eq("medium") 25 | ensure 26 | HackerOne::Client.low_range = HackerOne::Client::DEFAULT_LOW_RANGE 27 | HackerOne::Client.medium_range = HackerOne::Client::DEFAULT_MEDIUM_RANGE 28 | end 29 | end 30 | 31 | it "calculates payments" do 32 | expect(report.payment_total).to eq(750) 33 | end 34 | 35 | it "returns reporter info" do 36 | expect(report.reporter).to be_kind_of(Hash) 37 | end 38 | 39 | it "maps results to the owasp top 10" do 40 | expect(report.classification_label).to eq("A6-DataExposure") 41 | end 42 | 43 | describe "#weakness" do 44 | it "returns a weakness instance" do 45 | expect(report.weakness).to be_a(HackerOne::Client::Weakness) 46 | end 47 | end 48 | 49 | describe "#assign_to_user" do 50 | it "can assign to users" do 51 | VCR.use_cassette(:assign_report_to_user) do 52 | report.assign_to_user "esjee" 53 | end 54 | 55 | expect(report.assignee.username).to eq "esjee" 56 | end 57 | 58 | it "fails if the API user doesn't have permission" do 59 | expect do 60 | VCR.use_cassette(:assign_report_to_user_no_permission) do 61 | report.assign_to_user "esjee" 62 | end 63 | end.to raise_error RuntimeError 64 | end 65 | end 66 | 67 | describe "#assign_to_group" do 68 | it "can assign to groups" do 69 | VCR.use_cassette(:assign_report_to_group) do 70 | report.assign_to_group "Admin" 71 | end 72 | 73 | expect(report.assignee).to be_present 74 | end 75 | 76 | it "fails if the API user doesn't have permission" do 77 | expect do 78 | VCR.use_cassette(:assign_report_to_group_no_permission) do 79 | report.assign_to_group "Admin" 80 | end 81 | end.to raise_error RuntimeError 82 | end 83 | end 84 | 85 | describe "#unassign" do 86 | it "can assign to nobody" do 87 | VCR.use_cassette(:assign_report_to_nobody) do 88 | report.unassign 89 | end 90 | 91 | expect(report.assignee).to_not be_present 92 | end 93 | 94 | it "fails if the API user doesn't have permission" do 95 | expect do 96 | VCR.use_cassette(:assign_report_to_nobody_no_permission) do 97 | report.unassign 98 | end 99 | end.to raise_error RuntimeError 100 | end 101 | end 102 | 103 | describe "#award_bounty" do 104 | it "creates a bounty" do 105 | result = VCR.use_cassette(:award_a_bounty) do 106 | report.award_bounty( 107 | message: "Thanks for the great report!", 108 | amount: 1330, 109 | bonus_amount: 7 110 | ) 111 | end 112 | 113 | expect(result).to be_a HackerOne::Client::Bounty 114 | expect(result.amount).to eq "1330.00" 115 | expect(result.bonus_amount).to eq "7.00" 116 | expect(result.awarded_amount).to eq "1330.00" 117 | expect(result.awarded_bonus_amount).to eq "7.00" 118 | expect(result.awarded_currency).to eq "USD" 119 | end 120 | end 121 | 122 | describe "#suggest_award" do 123 | it "creates a bounty" do 124 | result = VCR.use_cassette(:suggest_a_bounty) do 125 | report.suggest_bounty( 126 | message: "This report is great, I think we should award a high bounty.", 127 | amount: 5000, 128 | bonus_amount: 2500 129 | ) 130 | end 131 | 132 | expect(result).to be_a HackerOne::Client::Activities::BountySuggested 133 | expect(result.message).to eq "This report is great, I think we should award a high bounty." 134 | expect(result.bounty_amount).to eq "5,000" 135 | expect(result.bonus_amount).to eq "2,500" 136 | expect(result.created_at).to be_present 137 | end 138 | end 139 | 140 | describe "#award_swag" do 141 | it "creates a bounty" do 142 | result = VCR.use_cassette(:award_swag) do 143 | report.award_swag( 144 | message: "Enjoy this cool swag!", 145 | ) 146 | end 147 | 148 | expect(result).to be_a HackerOne::Client::Swag 149 | expect(result.sent).to eq false 150 | expect(result.created_at).to be_present 151 | end 152 | end 153 | 154 | context "#state_change" do 155 | it "marks a report as triaged" do 156 | VCR.use_cassette(:stage_change) do 157 | expect(report.state_change(:triaged)).to_not be_nil 158 | end 159 | end 160 | 161 | it "marks a report as duplicate with the original report" do 162 | VCR.use_cassette(:dup) do 163 | expect(report.state_change(:duplicate, "totally a dup", original_report_id: "302")).to_not be_nil 164 | end 165 | end 166 | 167 | HackerOne::Client::Report::STATES_REQUIRING_STATE_CHANGE_MESSAGE.each do |state| 168 | it "raises an error if no message is supplied for #{state} actions" do 169 | expect { report.state_change(state) }.to raise_error(ArgumentError) 170 | end 171 | end 172 | 173 | (HackerOne::Client::Report::STATES - HackerOne::Client::Report::STATES_REQUIRING_STATE_CHANGE_MESSAGE).each do |state| 174 | it "does not raises an error if no message is supplied for #{state} actions" do 175 | VCR.use_cassette(:stage_change) do 176 | expect { report.state_change(state) }.to_not raise_error 177 | end 178 | end 179 | end 180 | end 181 | 182 | context "#add_report_reference" do 183 | it "adds an issue reference" do 184 | VCR.use_cassette(:add_report_reference) do 185 | expect(report.add_report_reference("fooooo")).to_not be_nil 186 | end 187 | 188 | expect(report.issue_tracker_reference_id).to eq "fooooo" 189 | end 190 | end 191 | 192 | context "#add_comment" do 193 | it "adds an internal comment by default" do 194 | VCR.use_cassette(:add_comment) do 195 | comment = report.add_comment("I am an internal comment") 196 | expect(comment).to be_a(HackerOne::Client::Activities::CommentAdded) 197 | expect(comment.internal).to be true 198 | end 199 | end 200 | 201 | it "allows comments for all participants" do 202 | VCR.use_cassette(:add_public_comment) do 203 | comment = report.add_comment("I am not an internal comment", internal: false) 204 | expect(comment).to be_a(HackerOne::Client::Activities::CommentAdded) 205 | expect(comment.internal).to be false 206 | end 207 | end 208 | end 209 | 210 | describe "#attachments" do 211 | it "returns a list of attachments" do 212 | expect(report.attachments).to all(be_an(HackerOne::Client::Attachment)) 213 | end 214 | end 215 | 216 | 217 | describe "#activities" do 218 | it "returns a list of activities" do 219 | expect(report.activities).to all(be_an(HackerOne::Client::Activities::Activity)) 220 | end 221 | end 222 | 223 | describe "#structured_scope" do 224 | it "returns a structured_scope" do 225 | scope = report.structured_scope 226 | expect(scope).to be_an(HackerOne::Client::StructuredScope) 227 | expect(scope.asset_type).to eq("url") 228 | end 229 | end 230 | 231 | describe "#on_state_change_hooks" do 232 | after do 233 | described_class.clear_on_state_change_hooks 234 | end 235 | 236 | it "triggers hooks on state changes" do 237 | described_class.add_on_state_change_hook ->(_report, _old_state, _new_state) do 238 | # NOOP, just leaving it hear to demonstrate the multiple hooks run 239 | end 240 | described_class.add_on_state_change_hook ->(report, old_state, new_state) do 241 | if new_state == "triaged" && report.assignee.nil? 242 | report.assign_to_user "esjee" 243 | end 244 | end 245 | 246 | VCR.use_cassette(:triage_and_hook_assign_report_to_user) do 247 | report.triage("fooooo") 248 | end 249 | 250 | expect(report.state).to eq "triaged" 251 | expect(report.assignee).to be_present 252 | expect(report.assignee.username).to eq "esjee" 253 | end 254 | end 255 | 256 | describe "#severity" do 257 | it "updates severity" do 258 | VCR.use_cassette(:update_severity) do 259 | report.update_severity(rating: :high) 260 | expect(report.severity[:rating]).to eq(:high) 261 | end 262 | end 263 | 264 | it "errors on invalid severity" do 265 | expect { report.update_severity(rating: :invalid) }.to raise_error "Invalid severity rating" 266 | end 267 | end 268 | 269 | describe "#lock" do 270 | it "locks a report" do 271 | VCR.use_cassette(:lock_report) do 272 | # to lock a report once must first resolve it 273 | expect(report.state_change(:resolved)).to_not be_nil 274 | response = report.lock! 275 | expect(response).to be_a(HackerOne::Client::Activities::ReportLocked) 276 | end 277 | end 278 | 279 | it "raises an error if report isn't closed" do 280 | expect { report.lock! }.to raise_error "Report must be closed before locking" 281 | end 282 | end 283 | end 284 | -------------------------------------------------------------------------------- /spec/hackerone/client/reporter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Report do 6 | let(:api) { HackerOne::Client::Api.new("github") } 7 | 8 | before(:all) do 9 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 10 | ENV["HACKERONE_TOKEN"] = "bar" 11 | end 12 | 13 | let(:reporters) do 14 | VCR.use_cassette(:reporters) do 15 | api.reporters 16 | end 17 | end 18 | 19 | let(:reporter) do 20 | reporters.first 21 | end 22 | 23 | it "returns a collection" do 24 | expect(reporters).to be_kind_of(Array) 25 | expect(reporters.size).to eq(2) 26 | end 27 | 28 | it "returns id" do 29 | expect(reporter.id).to be_present 30 | expect(reporter.id).to eq("3683") 31 | end 32 | 33 | it "returns disabled?" do 34 | expect(reporter.disabled?).to eq(false) 35 | end 36 | 37 | it "returns username" do 38 | expect(reporter.username).to eq("demo-hacker") 39 | end 40 | 41 | it "returns name" do 42 | expect(reporter.name).to eq("Demo Hacker") 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/hackerone/client/swag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Swag do 6 | let(:api) { HackerOne::Client::Api.new("github") } 7 | 8 | before(:all) do 9 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 10 | ENV["HACKERONE_TOKEN"] = "bar" 11 | end 12 | 13 | let(:program) do 14 | VCR.use_cassette(:programs) do 15 | HackerOne::Client::Program.find "github" 16 | end 17 | end 18 | 19 | let(:swags) do 20 | VCR.use_cassette(:swag) do 21 | program.swag 22 | end 23 | end 24 | 25 | let(:swag) { swags[0] } 26 | 27 | it "returns a collection" do 28 | expect(swags).to be_kind_of(Array) 29 | expect(swags.size).to eq(8) 30 | end 31 | 32 | it "returns id" do 33 | expect(swag.id).to be_present 34 | expect(swag.id).to eq("3377") 35 | end 36 | 37 | it "returns sent?" do 38 | expect(swag.sent?).to be(false) 39 | end 40 | 41 | describe "address" do 42 | it "returns an address if present" do 43 | address = swag.address 44 | expect(address).to be_present 45 | expect(address).to be_kind_of(HackerOne::Client::Address) 46 | end 47 | 48 | it "returns nil if not present" do 49 | address = swags[1].address 50 | expect(address).to be_nil 51 | end 52 | end 53 | 54 | 55 | describe "user" do 56 | it "returns a user" do 57 | user = swag.user 58 | expect(user).to be_present 59 | expect(user).to be_kind_of(HackerOne::Client::User) 60 | end 61 | end 62 | 63 | describe "mark_as_sent!" do 64 | it "should mark the swag as sent" do 65 | VCR.use_cassette(:swag_sent) do 66 | result = swag.mark_as_sent! 67 | expect(result).to be_present 68 | expect(result.sent?).to be true 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/hackerone/client/user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::User do 6 | before(:all) do 7 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 8 | ENV["HACKERONE_TOKEN"] = "bar" 9 | end 10 | 11 | describe "find" do 12 | it "returns a user" do 13 | user = VCR.use_cassette(:user_find_fransrosen) do 14 | described_class.find "fransrosen" 15 | end 16 | 17 | expect(user.reputation).to eq 15033 18 | expect(user.signal).to be_within(0.1).of(6.4) 19 | expect(user.impact).to be_within(0.1).of(22.6) 20 | expect(user.username).to eq "fransrosen" 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/hackerone/client/weakness_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe HackerOne::Client::Weakness do 6 | describe ".extract_cwe_number" do 7 | context "with invalid input" do 8 | it do 9 | expect { described_class.extract_cwe_number("1337") 10 | .to raise_error StandardError::ArgumentError 11 | } 12 | end 13 | end 14 | 15 | context "with CAPEC label" do 16 | it do 17 | expect { described_class.extract_cwe_number("CAPEC-1337") } 18 | .to raise_error HackerOne::Client::Weakness::NotAnOwaspWeaknessError 19 | end 20 | end 21 | 22 | context "with valid input" do 23 | it { expect(described_class.extract_cwe_number("CWE-134")).to eq(134) } 24 | end 25 | end 26 | 27 | describe "#to_cwe" do 28 | it "returns the external_id" do 29 | expect(described_class.new(external_id: "CWE-134").to_cwe) 30 | .to eq("CWE-134") 31 | end 32 | end 33 | 34 | describe "#to_owasp" do 35 | subject { described_class.new(external_id: cwe).to_owasp } 36 | 37 | context "unmappable CWE" do 38 | let(:cwe) { "CWE-33" } 39 | 40 | it { is_expected.to eq("A0-Other") } 41 | end 42 | 43 | context "mappable CWE" do 44 | let(:cwe) { "CWE-77" } 45 | 46 | it { is_expected.to eq("A1-Injection") } 47 | end 48 | 49 | it "falls back to name matching when external ID is nil" do 50 | classification = described_class.new(external_id: nil, name: "Command Injection").to_owasp 51 | expect(classification).to eq("A1-Injection") 52 | end 53 | 54 | it "falls A0-Other when no weakness data is avaialble" do 55 | classification = described_class.new({}).to_owasp 56 | expect(classification).to eq("A0-Other") 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/hackerone/client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | require "time" 5 | 6 | RSpec.describe HackerOne::Client do 7 | let(:api) { HackerOne::Client::Api.new("github") } 8 | let(:point_in_time) { DateTime.parse("2017-02-11T16:00:44-10:00") } 9 | 10 | before(:all) do 11 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 12 | ENV["HACKERONE_TOKEN"] = "bar" 13 | end 14 | 15 | context "configuration" do 16 | it "rejects invalid range values for risk classification" do 17 | begin 18 | expect { HackerOne::Client.low_range = "fred" }.to raise_error(ArgumentError) 19 | expect { HackerOne::Client.low_range = nil }.to raise_error(ArgumentError) 20 | expect { HackerOne::Client.low_range = 1..10000 }.to_not raise_error 21 | ensure 22 | HackerOne::Client.low_range = HackerOne::Client::DEFAULT_LOW_RANGE 23 | end 24 | end 25 | 26 | it "requires credential env vars" do 27 | begin 28 | ENV["HACKERONE_TOKEN_NAME"] = nil 29 | ENV["HACKERONE_TOKEN"] = nil 30 | expect { 31 | api.report(200) 32 | }.to raise_error(HackerOne::Client::NotConfiguredError) 33 | ensure 34 | ENV["HACKERONE_TOKEN_NAME"] = "foo" 35 | ENV["HACKERONE_TOKEN"] = "bar" 36 | end 37 | end 38 | end 39 | 40 | context "#report" do 41 | it "fetches and populates a report" do 42 | VCR.use_cassette(:report) do 43 | expect(api.report(200)).to_not be_nil 44 | end 45 | end 46 | 47 | it "raises an exception if a report is not found" do 48 | VCR.use_cassette(:missing_report) do 49 | expect { api.report(404) }.to raise_error(ArgumentError) 50 | end 51 | end 52 | 53 | it "raises an error if hackerone 500s" do 54 | VCR.use_cassette(:server_error) do 55 | expect { api.report(500) }.to raise_error(RuntimeError) 56 | end 57 | end 58 | end 59 | 60 | context "#create_report" do 61 | it "raises an error if no program is supplied" do 62 | expect { 63 | HackerOne::Client::Api.new.create_report(title: "hi", summary: "hi", impact: "string", severity_rating: "none", source: "api") 64 | }.to raise_error(ArgumentError) 65 | end 66 | 67 | it "creates a new report" do 68 | VCR.use_cassette(:create_report) do 69 | expect(api.create_report(title: "hi", summary: "hi", impact: "string", severity_rating: "none", source: "api")).to_not be_nil 70 | end 71 | end 72 | 73 | it "raises an error if report was not created" do 74 | VCR.use_cassette(:create_report_invalid) do 75 | expect { 76 | api.create_report(title: "hi", summary: "hi", impact: "string", severity_rating: "invalid_severity", source: "api") 77 | }.to raise_error(ArgumentError) 78 | end 79 | end 80 | end 81 | 82 | context "#reports" do 83 | it "raises an error if no program is supplied" do 84 | expect { HackerOne::Client::Api.new.reports }.to raise_error(ArgumentError) 85 | end 86 | 87 | it "returns new reports for a default program as default" do 88 | begin 89 | HackerOne::Client.program = "github" 90 | VCR.use_cassette(:report_list) do 91 | expect(HackerOne::Client::Api.new.reports(since: point_in_time)).to_not be_empty 92 | end 93 | ensure 94 | HackerOne::Client.program = nil 95 | end 96 | end 97 | 98 | it "returns new reports for a given program as default" do 99 | VCR.use_cassette(:report_list) do 100 | expect(api.reports(since: point_in_time)).to_not be_empty 101 | end 102 | end 103 | 104 | it "returns an empty array if no reports are found" do 105 | VCR.use_cassette(:empty_report_list) do 106 | expect(api.reports(since: point_in_time)).to be_empty 107 | end 108 | end 109 | 110 | it "returns triaged reports for a default program" do 111 | begin 112 | HackerOne::Client.program = "github" 113 | VCR.use_cassette(:report_list_triaged) do 114 | expect(HackerOne::Client::Api.new.reports(since: point_in_time, state: :triaged)).to_not be_empty 115 | end 116 | ensure 117 | HackerOne::Client.program = nil 118 | end 119 | end 120 | 121 | it "returns triaged reports for a given program" do 122 | VCR.use_cassette(:report_list_triaged) do 123 | expect(api.reports(since: point_in_time, state: :triaged)).to_not be_empty 124 | end 125 | end 126 | 127 | it "returns reports created before a certain date" do 128 | VCR.use_cassette(:report_list_before) do 129 | reports = api.reports(before: point_in_time, since: nil) 130 | expect(reports).to_not be_empty 131 | reports.map(&:created_at).each do |r| 132 | expect(Time.parse(r)).to be < point_in_time 133 | end 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/setup" 4 | require "hackerone/client" 5 | require "pry" 6 | require "vcr" 7 | require "webmock/rspec" 8 | 9 | RSpec.configure do |config| 10 | config.example_status_persistence_file_path = ".rspec_status" 11 | 12 | config.expect_with :rspec do |c| 13 | c.syntax = :expect 14 | end 15 | end 16 | 17 | VCR.configure do |config| 18 | config.cassette_library_dir = "fixtures/vcr_cassettes" 19 | config.hook_into :webmock 20 | end 21 | --------------------------------------------------------------------------------