{"action":"created","comment":{"url":"https://api.github.com/repos/cossacklabs/acra/pulls/comments/243989940","pull_request_review_id":187883298,"id":243989940,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI0Mzk4OTk0MA==","diff_hunk":"@@ -0,0 +1,268 @@\n+package common\n+\n+import (\n+\t\"bytes\"\n+\t\"encoding/json\"\n+\t\"github.com/cossacklabs/acra/logging\"\n+\t\"github.com/cossacklabs/acra/sqlparser\"\n+\tlog \"github.com/sirupsen/logrus\"\n+\t\"io\"\n+\t\"os\"\n+\t\"os/signal\"\n+\t\"strings\"\n+\t\"sync\"\n+\t\"syscall\"\n+\t\"time\"\n+)\n+\n+// DefaultSerializationTimeout defines a default ticker' timeout\n+const DefaultSerializationTimeout = time.Second\n+\n+// QueryInfo defines format of exporting query into file\n+type QueryInfo struct {\n+\tRawQuery    string `json:\"raw_query\"`\n+\tIsForbidden bool   `json:\"_blacklisted_by_web_config\"`\n+}\n+\n+// LogStorage defines basic storage that should be used by QueryWriter\n+type LogStorage interface {\n+\tio.Closer\n+\tReadAll() ([]byte, error)\n+\tWriteAll([]byte) error\n+\tAppend([]byte) error\n+}\n+\n+// QueryWriter is a mechanism that provides dumping input queries in background.\n+// It can be used as separate component or as one of censor's handlers\n+type QueryWriter struct {\n+\tQueries              []*QueryInfo\n+\tlogStorage           LogStorage\n+\tqueryIndex           int\n+\tmutex                *sync.Mutex\n+\tsignalBackgroundExit chan bool\n+\tserializationTimeout time.Duration\n+\tserializationTicker  *time.Ticker\n+\tlogger               *log.Entry\n+}\n+\n+// NewFileQueryWriter creates QueryWriter instance\n+func NewFileQueryWriter(filePath string) (*QueryWriter, error) {\n+\t// create writer\n+\twriter := &QueryWriter{}\n+\tstorage, err := NewFileLogStorage(filePath)\n+\tif err != nil {\n+\t\twriter.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Can't create QueryWriter instance\")\n+\t}\n+\twriter.logStorage = storage\n+\twriter.queryIndex = 0\n+\twriter.mutex = &sync.Mutex{}\n+\t// signals\n+\tsignalShutdown := make(chan os.Signal, 2)\n+\tsignal.Notify(signalShutdown, os.Interrupt, syscall.SIGTERM)\n+\tsignalBackgroundExit := make(chan bool)\n+\twriter.signalBackgroundExit = signalBackgroundExit\n+\n+\twriter.serializationTimeout = DefaultSerializationTimeout\n+\twriter.serializationTicker = time.NewTicker(DefaultSerializationTimeout)\n+\twriter.logger = log.WithField(\"internal_object\", \"querywriter\")\n+\n+\t// read existing queries\n+\terr = writer.readStoredQueries()\n+\tif err != nil {\n+\t\twriter.logger.WithError(ErrCantReadQueriesFromFileError).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Can't create QueryWriter instance\")\n+\t\twriter.logStorage.Close()\n+\t\treturn nil, err\n+\t}\n+\n+\t//handling goroutine\n+\tgo func() {\n+\t\tfor {\n+\t\t\tselect {\n+\t\t\tcase <-writer.serializationTicker.C:\n+\t\t\t\terr := writer.dumpBufferedQueries()\n+\t\t\t\tif err != nil {\n+\t\t\t\t\twriter.logger.WithError(ErrComplexSerializationError).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Can't dump buffered queries\")\n+\t\t\t\t}\n+\t\t\t\twriter.serializationTicker.Stop()\n+\t\t\t\twriter.serializationTicker = time.NewTicker(writer.serializationTimeout)\n+\t\t\tcase <-signalBackgroundExit:\n+\t\t\t\twriter.serializationTicker.Stop()\n+\t\t\t\terr := writer.logStorage.Close()\n+\t\t\t\tif err != nil {\n+\t\t\t\t\twriter.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Error occurred on exit QueryWriter instance\")\n+\t\t\t\t}\n+\t\t\tcase <-signalShutdown:\n+\t\t\t\twriter.serializationTicker.Stop()\n+\t\t\t\terr := writer.logStorage.Close()\n+\t\t\t\tif err != nil {\n+\t\t\t\t\twriter.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Error occurred on shutdown QueryWriter instance\")\n+\t\t\t\t}\n+\t\t\t\treturn\n+\t\t\tdefault:\n+\t\t\t\t//do nothing. This means that channel has no data to read yet\n+\t\t\t}\n+\t\t}\n+\t}()\n+\treturn writer, nil\n+}\n+\n+// SetSerializationTimeout sets timeout of dumping captured queries to the file.\n+func (queryWriter *QueryWriter) SetSerializationTimeout(timeout time.Duration) {\n+\tqueryWriter.serializationTimeout = timeout\n+}\n+\n+// GetSerializationTimeout gets timeout of dumping captured queries to the file.\n+func (queryWriter *QueryWriter) GetSerializationTimeout() time.Duration {\n+\treturn queryWriter.serializationTimeout\n+}\n+\n+// DumpQueries writes all queries into file.\n+func (queryWriter *QueryWriter) DumpQueries() error {\n+\trawData := queryWriter.serializeQueries(queryWriter.Queries)\n+\tif err := queryWriter.logStorage.WriteAll(rawData); err != nil {\n+\t\tqueryWriter.logger.WithError(ErrCantOpenFileError).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Can't dump queries to storage\")\n+\t\treturn err\n+\t}\n+\treturn nil\n+}\n+\n+// Release dumps all Captured queries to file and resets Captured queries list.\n+func (queryWriter *QueryWriter) Release() {\n+\tqueryWriter.DumpQueries()\n+\tqueryWriter.signalBackgroundExit <- true\n+\tqueryWriter.reset()\n+}\n+\n+func (queryWriter *QueryWriter) reset() {\n+\tqueryWriter.Queries = nil\n+}\n+\n+func (queryWriter *QueryWriter) readStoredQueries() error {\n+\tqueryWriter.mutex.Lock()\n+\tdefer queryWriter.mutex.Unlock()\n+\n+\tq, err := queryWriter.deserializeQueries()\n+\tif err != nil {\n+\t\tqueryWriter.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorIOError).Errorln(\"Can't read stored queries\")\n+\t\treturn err\n+\t}\n+\tqueryWriter.Queries = q\n+\treturn nil\n+}\n+\n+func (queryWriter *QueryWriter) dumpBufferedQueries() error {\n+\tqueryWriter.mutex.Lock()\n+\tdefer queryWriter.mutex.Unlock()\n+\tpartialRawData := queryWriter.serializeQueries(queryWriter.Queries[queryWriter.queryIndex:])\n+\tif err := queryWriter.logStorage.Append(partialRawData); err != nil {\n+\t\treturn err\n+\t}\n+\tqueryWriter.queryIndex = len(queryWriter.Queries)\n+\treturn nil\n+}\n+\n+func (queryWriter *QueryWriter) deserializeQueries() ([]*QueryInfo, error) {\n+\tbufferBytes, err := queryWriter.logStorage.ReadAll()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tvar queries []*QueryInfo\n+\tif len(bufferBytes) != 0 {\n+\t\tfor _, line := range bytes.Split(bufferBytes, []byte{'\\n'}) {\n+\t\t\tif len(line) == 0 {\n+\t\t\t\tcontinue\n+\t\t\t}\n+\t\t\tvar oneQuery QueryInfo\n+\t\t\tif err = json.Unmarshal(line, &oneQuery); err != nil {\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\t\t\tqueries = append(queries, &oneQuery)\n+\t\t}\n+\t}\n+\treturn queries, nil\n+}\n+\n+func (queryWriter *QueryWriter) serializeQueries(queries []*QueryInfo) []byte {\n+\tvar linesToAppend []byte\n+\tvar tempQueryInfo = &QueryInfo{}\n+\tfor _, queryInfo := range queries {\n+\t\ttempQueryInfo.RawQuery = queryInfo.RawQuery\n+\t\ttempQueryInfo.IsForbidden = queryInfo.IsForbidden\n+\t\tjsonQueryInfo, err := json.Marshal(tempQueryInfo)\n+\t\tif err != nil {\n+\t\t\tqueryWriter.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorQuerySerializeError).Errorln(\"Can't serialize stored queries\")\n+\t\t}\n+\t\tif len(jsonQueryInfo) > 0 {\n+\t\t\tjsonQueryInfo = append(jsonQueryInfo, '\\n')\n+\t\t\tlinesToAppend = append(linesToAppend, jsonQueryInfo...)\n+\t\t}\n+\t}\n+\treturn linesToAppend\n+}\n+\n+// GetAllInputQueries returns a list of non-masked RawQueries.\n+func (queryWriter *QueryWriter) GetAllInputQueries() []string {\n+\tvar queries []string\n+\tfor _, queryInfo := range queryWriter.Queries {\n+\t\tqueries = append(queries, queryInfo.RawQuery)\n+\t}\n+\treturn queries\n+}\n+\n+// RedactAndMarkQueryAsForbidden redacts query first, then calls CheckQuery\n+func (queryWriter *QueryWriter) RedactAndMarkQueryAsForbidden(query string) {\n+\t_, queryWithHiddenValues, _, err := HandleRawSQLQuery(query)\n+\tif err != nil {\n+\t\treturn\n+\t}\n+\tqueryWriter.MarkQueryAsForbidden(queryWithHiddenValues)\n+}\n+\n+// MarkQueryAsForbidden marks particular query as forbidden.\n+// It will be written to file on Stop, Reset or Release.\n+// Expects redacted query\n+func (queryWriter *QueryWriter) MarkQueryAsForbidden(queryWithHiddenValues string) {\n+\tfor index, queryInfo := range queryWriter.Queries {\n+\t\tif strings.EqualFold(queryWithHiddenValues, queryInfo.RawQuery) {\n+\t\t\tqueryWriter.Queries[index].IsForbidden = true\n+\t\t}\n+\t}\n+}\n+\n+// GetForbiddenQueries returns a list of non-masked forbidden RawQueries.\n+func (queryWriter *QueryWriter) GetForbiddenQueries() []string {\n+\tvar forbiddenQueries []string\n+\tfor _, queryInfo := range queryWriter.Queries {\n+\t\tif queryInfo.IsForbidden == true {\n+\t\t\tforbiddenQueries = append(forbiddenQueries, queryInfo.RawQuery)\n+\t\t}\n+\t}\n+\treturn forbiddenQueries\n+}\n+\n+// RedactAndCheckQuery redacts query first, then calls CheckQuery\n+func (queryWriter *QueryWriter) RedactAndCheckQuery(query string) (bool, error) {\n+\t_, queryWithHiddenValues, parsedQuery, err := HandleRawSQLQuery(query)\n+\tif err != nil {\n+\t\treturn true, nil\n+\t}\n+\treturn queryWriter.CheckQuery(queryWithHiddenValues, parsedQuery)\n+}\n+\n+// CheckQuery returns \"yes\" if Query was already captured, no otherwise.\n+// Expects already redacted queries\n+func (queryWriter *QueryWriter) CheckQuery(queryWithHiddenValues string, parsedQuery sqlparser.Statement) (bool, error) {\n+\t_ = parsedQuery\n+\n+\t//skip already captured queries\n+\tfor _, queryInfo := range queryWriter.Queries {\n+\t\tif strings.EqualFold(queryInfo.RawQuery, queryWithHiddenValues) {\n+\t\t\treturn true, nil\n+\t\t}\n+\t}\n+\tqueryInfo := &QueryInfo{}\n+\tqueryInfo.RawQuery = queryWithHiddenValues\n+\tqueryInfo.IsForbidden = false\n+\tqueryWriter.Queries = append(queryWriter.Queries, queryInfo)","path":"acra-censor/common/logging_logic.go","position":266,"original_position":266,"commit_id":"e4a67cf67ece6e56e32357f046c218c6fa18ea33","original_commit_id":"e4a67cf67ece6e56e32357f046c218c6fa18ea33","user":{"login":"Lagovas","id":6237700,"node_id":"MDQ6VXNlcjYyMzc3MDA=","avatar_url":"https://avatars3.githubusercontent.com/u/6237700?v=4","gravatar_id":"","url":"https://api.github.com/users/Lagovas","html_url":"https://github.com/Lagovas","followers_url":"https://api.github.com/users/Lagovas/followers","following_url":"https://api.github.com/users/Lagovas/following{/other_user}","gists_url":"https://api.github.com/users/Lagovas/gists{/gist_id}","starred_url":"https://api.github.com/users/Lagovas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Lagovas/subscriptions","organizations_url":"https://api.github.com/users/Lagovas/orgs","repos_url":"https://api.github.com/users/Lagovas/repos","events_url":"https://api.github.com/users/Lagovas/events{/privacy}","received_events_url":"https://api.github.com/users/Lagovas/received_events","type":"User","site_admin":false},"body":"Lock on write","created_at":"2018-12-26T12:29:31Z","updated_at":"2018-12-26T12:32:33Z","html_url":"https://github.com/cossacklabs/acra/pull/295#discussion_r243989940","pull_request_url":"https://api.github.com/repos/cossacklabs/acra/pulls/295","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/cossacklabs/acra/pulls/comments/243989940"},"html":{"href":"https://github.com/cossacklabs/acra/pull/295#discussion_r243989940"},"pull_request":{"href":"https://api.github.com/repos/cossacklabs/acra/pulls/295"}}},"pull_request":{"url":"https://api.github.com/repos/cossacklabs/acra/pulls/295","id":240400074,"node_id":"MDExOlB1bGxSZXF1ZXN0MjQwNDAwMDc0","html_url":"https://github.com/cossacklabs/acra/pull/295","diff_url":"https://github.com/cossacklabs/acra/pull/295.diff","patch_url":"https://github.com/cossacklabs/acra/pull/295.patch","issue_url":"https://api.github.com/repos/cossacklabs/acra/issues/295","number":295,"state":"open","locked":false,"title":"T864 - Separate log file for unparsed queries","user":{"login":"storojs72","id":20335747,"node_id":"MDQ6VXNlcjIwMzM1NzQ3","avatar_url":"https://avatars1.githubusercontent.com/u/20335747?v=4","gravatar_id":"","url":"https://api.github.com/users/storojs72","html_url":"https://github.com/storojs72","followers_url":"https://api.github.com/users/storojs72/followers","following_url":"https://api.github.com/users/storojs72/following{/other_user}","gists_url":"https://api.github.com/users/storojs72/gists{/gist_id}","starred_url":"https://api.github.com/users/storojs72/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/storojs72/subscriptions","organizations_url":"https://api.github.com/users/storojs72/orgs","repos_url":"https://api.github.com/users/storojs72/repos","events_url":"https://api.github.com/users/storojs72/events{/privacy}","received_events_url":"https://api.github.com/users/storojs72/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-12-21T08:09:24Z","updated_at":"2018-12-26T12:32:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c5ab29402829ed4c33aa21350eb2e8027e2d6243","assignee":null,"assignees":[],"requested_reviewers":[{"login":"vixentael","id":2877920,"node_id":"MDQ6VXNlcjI4Nzc5MjA=","avatar_url":"https://avatars2.githubusercontent.com/u/2877920?v=4","gravatar_id":"","url":"https://api.github.com/users/vixentael","html_url":"https://github.com/vixentael","followers_url":"https://api.github.com/users/vixentael/followers","following_url":"https://api.github.com/users/vixentael/following{/other_user}","gists_url":"https://api.github.com/users/vixentael/gists{/gist_id}","starred_url":"https://api.github.com/users/vixentael/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vixentael/subscriptions","organizations_url":"https://api.github.com/users/vixentael/orgs","repos_url":"https://api.github.com/users/vixentael/repos","events_url":"https://api.github.com/users/vixentael/events{/privacy}","received_events_url":"https://api.github.com/users/vixentael/received_events","type":"User","site_admin":false},{"login":"shadinua","id":18161943,"node_id":"MDQ6VXNlcjE4MTYxOTQz","avatar_url":"https://avatars1.githubusercontent.com/u/18161943?v=4","gravatar_id":"","url":"https://api.github.com/users/shadinua","html_url":"https://github.com/shadinua","followers_url":"https://api.github.com/users/shadinua/followers","following_url":"https://api.github.com/users/shadinua/following{/other_user}","gists_url":"https://api.github.com/users/shadinua/gists{/gist_id}","starred_url":"https://api.github.com/users/shadinua/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shadinua/subscriptions","organizations_url":"https://api.github.com/users/shadinua/orgs","repos_url":"https://api.github.com/users/shadinua/repos","events_url":"https://api.github.com/users/shadinua/events{/privacy}","received_events_url":"https://api.github.com/users/shadinua/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":861780996,"node_id":"MDU6TGFiZWw4NjE3ODA5OTY=","url":"https://api.github.com/repos/cossacklabs/acra/labels/acra-censor","name":"acra-censor","color":"fcc5ba","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/cossacklabs/acra/pulls/295/commits","review_comments_url":"https://api.github.com/repos/cossacklabs/acra/pulls/295/comments","review_comment_url":"https://api.github.com/repos/cossacklabs/acra/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cossacklabs/acra/issues/295/comments","statuses_url":"https://api.github.com/repos/cossacklabs/acra/statuses/e4a67cf67ece6e56e32357f046c218c6fa18ea33","head":{"label":"storojs72:master","ref":"master","sha":"e4a67cf67ece6e56e32357f046c218c6fa18ea33","user":{"login":"storojs72","id":20335747,"node_id":"MDQ6VXNlcjIwMzM1NzQ3","avatar_url":"https://avatars1.githubusercontent.com/u/20335747?v=4","gravatar_id":"","url":"https://api.github.com/users/storojs72","html_url":"https://github.com/storojs72","followers_url":"https://api.github.com/users/storojs72/followers","following_url":"https://api.github.com/users/storojs72/following{/other_user}","gists_url":"https://api.github.com/users/storojs72/gists{/gist_id}","starred_url":"https://api.github.com/users/storojs72/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/storojs72/subscriptions","organizations_url":"https://api.github.com/users/storojs72/orgs","repos_url":"https://api.github.com/users/storojs72/repos","events_url":"https://api.github.com/users/storojs72/events{/privacy}","received_events_url":"https://api.github.com/users/storojs72/received_events","type":"User","site_admin":false},"repo":{"id":144154422,"node_id":"MDEwOlJlcG9zaXRvcnkxNDQxNTQ0MjI=","name":"acra","full_name":"storojs72/acra","private":false,"owner":{"login":"storojs72","id":20335747,"node_id":"MDQ6VXNlcjIwMzM1NzQ3","avatar_url":"https://avatars1.githubusercontent.com/u/20335747?v=4","gravatar_id":"","url":"https://api.github.com/users/storojs72","html_url":"https://github.com/storojs72","followers_url":"https://api.github.com/users/storojs72/followers","following_url":"https://api.github.com/users/storojs72/following{/other_user}","gists_url":"https://api.github.com/users/storojs72/gists{/gist_id}","starred_url":"https://api.github.com/users/storojs72/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/storojs72/subscriptions","organizations_url":"https://api.github.com/users/storojs72/orgs","repos_url":"https://api.github.com/users/storojs72/repos","events_url":"https://api.github.com/users/storojs72/events{/privacy}","received_events_url":"https://api.github.com/users/storojs72/received_events","type":"User","site_admin":false},"html_url":"https://github.com/storojs72/acra","description":"Network encryption proxy for database-based apps: strong selective encryption, SQL injections prevention, intrusion detection, honeypots","fork":true,"url":"https://api.github.com/repos/storojs72/acra","forks_url":"https://api.github.com/repos/storojs72/acra/forks","keys_url":"https://api.github.com/repos/storojs72/acra/keys{/key_id}","collaborators_url":"https://api.github.com/repos/storojs72/acra/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/storojs72/acra/teams","hooks_url":"https://api.github.com/repos/storojs72/acra/hooks","issue_events_url":"https://api.github.com/repos/storojs72/acra/issues/events{/number}","events_url":"https://api.github.com/repos/storojs72/acra/events","assignees_url":"https://api.github.com/repos/storojs72/acra/assignees{/user}","branches_url":"https://api.github.com/repos/storojs72/acra/branches{/branch}","tags_url":"https://api.github.com/repos/storojs72/acra/tags","blobs_url":"https://api.github.com/repos/storojs72/acra/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/storojs72/acra/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/storojs72/acra/git/refs{/sha}","trees_url":"https://api.github.com/repos/storojs72/acra/git/trees{/sha}","statuses_url":"https://api.github.com/repos/storojs72/acra/statuses/{sha}","languages_url":"https://api.github.com/repos/storojs72/acra/languages","stargazers_url":"https://api.github.com/repos/storojs72/acra/stargazers","contributors_url":"https://api.github.com/repos/storojs72/acra/contributors","subscribers_url":"https://api.github.com/repos/storojs72/acra/subscribers","subscription_url":"https://api.github.com/repos/storojs72/acra/subscription","commits_url":"https://api.github.com/repos/storojs72/acra/commits{/sha}","git_commits_url":"https://api.github.com/repos/storojs72/acra/git/commits{/sha}","comments_url":"https://api.github.com/repos/storojs72/acra/comments{/number}","issue_comment_url":"https://api.github.com/repos/storojs72/acra/issues/comments{/number}","contents_url":"https://api.github.com/repos/storojs72/acra/contents/{+path}","compare_url":"https://api.github.com/repos/storojs72/acra/compare/{base}...{head}","merges_url":"https://api.github.com/repos/storojs72/acra/merges","archive_url":"https://api.github.com/repos/storojs72/acra/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/storojs72/acra/downloads","issues_url":"https://api.github.com/repos/storojs72/acra/issues{/number}","pulls_url":"https://api.github.com/repos/storojs72/acra/pulls{/number}","milestones_url":"https://api.github.com/repos/storojs72/acra/milestones{/number}","notifications_url":"https://api.github.com/repos/storojs72/acra/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/storojs72/acra/labels{/name}","releases_url":"https://api.github.com/repos/storojs72/acra/releases{/id}","deployments_url":"https://api.github.com/repos/storojs72/acra/deployments","created_at":"2018-08-09T13:11:50Z","updated_at":"2018-12-26T11:07:46Z","pushed_at":"2018-12-26T11:07:43Z","git_url":"git://github.com/storojs72/acra.git","ssh_url":"git@github.com:storojs72/acra.git","clone_url":"https://github.com/storojs72/acra.git","svn_url":"https://github.com/storojs72/acra","homepage":"https://www.cossacklabs.com/acra/","size":7522,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"cossacklabs:master","ref":"master","sha":"8acb0b39ee49bd7b570edb29637b81ded904dec0","user":{"login":"cossacklabs","id":11052315,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExMDUyMzE1","avatar_url":"https://avatars1.githubusercontent.com/u/11052315?v=4","gravatar_id":"","url":"https://api.github.com/users/cossacklabs","html_url":"https://github.com/cossacklabs","followers_url":"https://api.github.com/users/cossacklabs/followers","following_url":"https://api.github.com/users/cossacklabs/following{/other_user}","gists_url":"https://api.github.com/users/cossacklabs/gists{/gist_id}","starred_url":"https://api.github.com/users/cossacklabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cossacklabs/subscriptions","organizations_url":"https://api.github.com/users/cossacklabs/orgs","repos_url":"https://api.github.com/users/cossacklabs/repos","events_url":"https://api.github.com/users/cossacklabs/events{/privacy}","received_events_url":"https://api.github.com/users/cossacklabs/received_events","type":"Organization","site_admin":false},"repo":{"id":73724173,"node_id":"MDEwOlJlcG9zaXRvcnk3MzcyNDE3Mw==","name":"acra","full_name":"cossacklabs/acra","private":false,"owner":{"login":"cossacklabs","id":11052315,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExMDUyMzE1","avatar_url":"https://avatars1.githubusercontent.com/u/11052315?v=4","gravatar_id":"","url":"https://api.github.com/users/cossacklabs","html_url":"https://github.com/cossacklabs","followers_url":"https://api.github.com/users/cossacklabs/followers","following_url":"https://api.github.com/users/cossacklabs/following{/other_user}","gists_url":"https://api.github.com/users/cossacklabs/gists{/gist_id}","starred_url":"https://api.github.com/users/cossacklabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cossacklabs/subscriptions","organizations_url":"https://api.github.com/users/cossacklabs/orgs","repos_url":"https://api.github.com/users/cossacklabs/repos","events_url":"https://api.github.com/users/cossacklabs/events{/privacy}","received_events_url":"https://api.github.com/users/cossacklabs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/cossacklabs/acra","description":"Database encryption proxy for data-driven apps: strong selective encryption, SQL injections prevention, intrusion detection, honeypots.","fork":false,"url":"https://api.github.com/repos/cossacklabs/acra","forks_url":"https://api.github.com/repos/cossacklabs/acra/forks","keys_url":"https://api.github.com/repos/cossacklabs/acra/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cossacklabs/acra/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cossacklabs/acra/teams","hooks_url":"https://api.github.com/repos/cossacklabs/acra/hooks","issue_events_url":"https://api.github.com/repos/cossacklabs/acra/issues/events{/number}","events_url":"https://api.github.com/repos/cossacklabs/acra/events","assignees_url":"https://api.github.com/repos/cossacklabs/acra/assignees{/user}","branches_url":"https://api.github.com/repos/cossacklabs/acra/branches{/branch}","tags_url":"https://api.github.com/repos/cossacklabs/acra/tags","blobs_url":"https://api.github.com/repos/cossacklabs/acra/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cossacklabs/acra/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cossacklabs/acra/git/refs{/sha}","trees_url":"https://api.github.com/repos/cossacklabs/acra/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cossacklabs/acra/statuses/{sha}","languages_url":"https://api.github.com/repos/cossacklabs/acra/languages","stargazers_url":"https://api.github.com/repos/cossacklabs/acra/stargazers","contributors_url":"https://api.github.com/repos/cossacklabs/acra/contributors","subscribers_url":"https://api.github.com/repos/cossacklabs/acra/subscribers","subscription_url":"https://api.github.com/repos/cossacklabs/acra/subscription","commits_url":"https://api.github.com/repos/cossacklabs/acra/commits{/sha}","git_commits_url":"https://api.github.com/repos/cossacklabs/acra/git/commits{/sha}","comments_url":"https://api.github.com/repos/cossacklabs/acra/comments{/number}","issue_comment_url":"https://api.github.com/repos/cossacklabs/acra/issues/comments{/number}","contents_url":"https://api.github.com/repos/cossacklabs/acra/contents/{+path}","compare_url":"https://api.github.com/repos/cossacklabs/acra/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cossacklabs/acra/merges","archive_url":"https://api.github.com/repos/cossacklabs/acra/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cossacklabs/acra/downloads","issues_url":"https://api.github.com/repos/cossacklabs/acra/issues{/number}","pulls_url":"https://api.github.com/repos/cossacklabs/acra/pulls{/number}","milestones_url":"https://api.github.com/repos/cossacklabs/acra/milestones{/number}","notifications_url":"https://api.github.com/repos/cossacklabs/acra/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cossacklabs/acra/labels{/name}","releases_url":"https://api.github.com/repos/cossacklabs/acra/releases{/id}","deployments_url":"https://api.github.com/repos/cossacklabs/acra/deployments","created_at":"2016-11-14T16:23:25Z","updated_at":"2018-12-26T08:47:02Z","pushed_at":"2018-12-26T11:07:45Z","git_url":"git://github.com/cossacklabs/acra.git","ssh_url":"git@github.com:cossacklabs/acra.git","clone_url":"https://github.com/cossacklabs/acra.git","svn_url":"https://github.com/cossacklabs/acra","homepage":"https://www.cossacklabs.com/acra/","size":7448,"stargazers_count":355,"watchers_count":355,"language":"Go","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":24,"mirror_url":null,"archived":false,"open_issues_count":7,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":24,"open_issues":7,"watchers":355,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cossacklabs/acra/pulls/295"},"html":{"href":"https://github.com/cossacklabs/acra/pull/295"},"issue":{"href":"https://api.github.com/repos/cossacklabs/acra/issues/295"},"comments":{"href":"https://api.github.com/repos/cossacklabs/acra/issues/295/comments"},"review_comments":{"href":"https://api.github.com/repos/cossacklabs/acra/pulls/295/comments"},"review_comment":{"href":"https://api.github.com/repos/cossacklabs/acra/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cossacklabs/acra/pulls/295/commits"},"statuses":{"href":"https://api.github.com/repos/cossacklabs/acra/statuses/e4a67cf67ece6e56e32357f046c218c6fa18ea33"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"Lagovas"}}