├── .gitignore ├── docs ├── .dockerignore ├── netlify.toml ├── src │ ├── pwa-512.png │ ├── components │ │ ├── images │ │ │ ├── day.png │ │ │ ├── night.png │ │ │ ├── closed.js │ │ │ ├── opened.js │ │ │ ├── help.svg │ │ │ ├── twitter-brands-block.svg │ │ │ ├── discord-brands-block.svg │ │ │ ├── twitter.svg │ │ │ ├── github.svg │ │ │ └── logo.svg │ │ ├── theme.js │ │ ├── mdxComponents │ │ │ ├── loading.js │ │ │ ├── anchor.js │ │ │ ├── LiveProvider.js │ │ │ ├── index.js │ │ │ └── codeBlock.js │ │ ├── index.js │ │ ├── search │ │ │ ├── styles.js │ │ │ ├── hitComps.js │ │ │ ├── input.js │ │ │ └── index.js │ │ ├── link.js │ │ ├── themeProvider.js │ │ ├── theme │ │ │ ├── index.js │ │ │ └── themeProvider.js │ │ ├── sidebar │ │ │ ├── treeNode.js │ │ │ ├── index.js │ │ │ └── tree.js │ │ ├── styles │ │ │ ├── Docs.js │ │ │ ├── Sidebar.js │ │ │ └── PageNavigationButtons.js │ │ ├── rightSidebar.js │ │ ├── DarkModeSwitch.js │ │ ├── layout.js │ │ └── NextPrevious.js │ ├── custom │ │ ├── styles │ │ │ └── styles.js │ │ └── config │ │ │ └── codeBlockLanguages.js │ ├── custom-sw-code.js │ ├── GithubLink.js │ ├── YoutubeEmbed.js │ ├── utils │ │ └── algolia.js │ ├── html.js │ └── templates │ │ └── docs.js ├── public │ ├── favicon.png │ └── images │ │ ├── ogp.png │ │ ├── sso.png │ │ ├── header.png │ │ ├── logo-white.png │ │ ├── about-us-screenshot.png │ │ └── twitter-brands-block.svg ├── content │ ├── images │ │ └── shisho-demo.gif │ ├── roadmap.md │ ├── index.md │ ├── shisho-action.md │ ├── shisho │ │ ├── learn-shisho.md │ │ ├── learn-shisho │ │ │ ├── 02-rule.md │ │ │ └── 01-pattern.md │ │ └── getting-started.md │ ├── shisho-dojo.md │ ├── changelog.md │ ├── shisho-cloud.md │ ├── shisho.md │ └── shisho-cloud │ │ ├── getting-started.md │ │ └── frequently-asked-questions.md ├── .prettierrc ├── .gitignore ├── .editorconfig ├── gatsby-browser.js ├── Dockerfile ├── .eslintrc.json ├── LICENSE ├── README.md ├── config.js ├── package.json ├── gatsby-node.js └── gatsby-config.js ├── src ├── cli │ ├── tests │ │ ├── mod.rs │ │ └── ruleset │ │ │ ├── generic │ │ │ ├── constraints │ │ │ │ ├── match.tf │ │ │ │ ├── unmatch.tf │ │ │ │ ├── match-pattern.yaml │ │ │ │ ├── not-match-pattern.yaml │ │ │ │ ├── not-match-regex-pattern.yaml │ │ │ │ ├── be-any-of.yaml │ │ │ │ ├── match-regex-pattern.yaml │ │ │ │ ├── match-any-of-regex-pattern.yaml │ │ │ │ ├── not-be-any-of.yaml │ │ │ │ ├── match-any-of-pattern.yaml │ │ │ │ ├── not-match-any-of-regex-pattern.yaml │ │ │ │ └── not-match-any-of-pattern.yaml │ │ │ ├── invalid_constraints │ │ │ │ ├── unmatch.tf │ │ │ │ ├── no-pattern-like.yaml │ │ │ │ ├── invalid-match-string.yaml │ │ │ │ ├── invalid-match-strings.yaml │ │ │ │ ├── ambiguous-pattern-use.yaml │ │ │ │ ├── mixed-pattern-like.yaml │ │ │ │ └── ambiguous-regex-pattern-use.yaml │ │ │ ├── nested_constraints │ │ │ │ ├── unmatch.without-inner.tf │ │ │ │ ├── unmatch.with-inner.tf │ │ │ │ ├── match.tf │ │ │ │ └── ruleset.yaml │ │ │ ├── encoding │ │ │ │ ├── utf_16le.go │ │ │ │ ├── shift_jis.go │ │ │ │ └── ruleset.yaml │ │ │ ├── shared_constraints │ │ │ │ ├── dockerfile │ │ │ │ ├── Dockerfile.test │ │ │ │ ├── test.Dockerfile │ │ │ │ └── ruleset.yaml │ │ │ └── mod.rs │ │ │ ├── hcl │ │ │ ├── comment │ │ │ │ ├── unmatch.tf │ │ │ │ ├── ruleset.yaml │ │ │ │ └── match.tf │ │ │ ├── unencrypted_ebs │ │ │ │ ├── unmatch.tf │ │ │ │ ├── match.tf │ │ │ │ └── ruleset.yaml │ │ │ ├── uncontrolled_ebs_encryption_key │ │ │ │ ├── match.tf │ │ │ │ ├── unmatch.tf │ │ │ │ └── ruleset.yaml │ │ │ └── mod.rs │ │ │ └── mod.rs │ ├── subcommand.rs │ ├── subcommand │ │ ├── completion.rs │ │ ├── find.rs │ │ └── check.rs │ ├── opts.rs │ ├── reporter.rs │ ├── reporter │ │ ├── json.rs │ │ └── sarif.rs │ └── encoding.rs ├── core │ ├── ruleset │ │ ├── assets │ │ │ ├── dumb │ │ │ ├── 1.yaml │ │ │ └── 2.yaml │ │ └── test.rs │ ├── matcher.rs │ ├── rewriter │ │ └── literal.rs │ ├── rewriter.rs │ ├── query.rs │ ├── matcher │ │ ├── state.rs │ │ └── literal.rs │ ├── language.rs │ ├── source.rs │ ├── pattern.rs │ └── tree.rs ├── lib.rs ├── core.rs ├── cli.rs └── main.rs ├── .vscode ├── extensions.json └── launch.json ├── rules └── docker.shisho.yaml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── coverage.yml │ ├── test.yml │ └── release.yml ├── Dockerfile ├── .gitmodules ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | -------------------------------------------------------------------------------- /docs/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | -------------------------------------------------------------------------------- /src/cli/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod ruleset; 3 | -------------------------------------------------------------------------------- /src/core/ruleset/assets/dumb: -------------------------------------------------------------------------------- 1 | this file should not be loaded -------------------------------------------------------------------------------- /docs/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | -------------------------------------------------------------------------------- /docs/src/pwa-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/src/pwa-512.png -------------------------------------------------------------------------------- /docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/public/favicon.png -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/match.tf: -------------------------------------------------------------------------------- 1 | resource "block" "volume" { 2 | attr = 1 3 | } 4 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/unmatch.tf: -------------------------------------------------------------------------------- 1 | resource "block" "volume" { 2 | attr = 2 3 | } 4 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/comment/unmatch.tf: -------------------------------------------------------------------------------- 1 | resource "comment_test" "volume" { 2 | size = 1 3 | } 4 | -------------------------------------------------------------------------------- /docs/public/images/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/public/images/ogp.png -------------------------------------------------------------------------------- /docs/public/images/sso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/public/images/sso.png -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/unmatch.tf: -------------------------------------------------------------------------------- 1 | resource "block" "volume" { 2 | attr = 2 3 | } 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::or_fun_call, clippy::expect_fun_call)] 2 | 3 | pub mod cli; 4 | pub mod core; 5 | -------------------------------------------------------------------------------- /docs/public/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/public/images/header.png -------------------------------------------------------------------------------- /docs/public/images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/public/images/logo-white.png -------------------------------------------------------------------------------- /docs/content/images/shisho-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/content/images/shisho-demo.gif -------------------------------------------------------------------------------- /docs/src/components/images/day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/src/components/images/day.png -------------------------------------------------------------------------------- /docs/src/components/images/night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/src/components/images/night.png -------------------------------------------------------------------------------- /docs/public/images/about-us-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/docs/public/images/about-us-screenshot.png -------------------------------------------------------------------------------- /docs/src/components/theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fonts: { 3 | mono: '"SF Mono", "Roboto Mono", Menlo, monospace', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/nested_constraints/unmatch.without-inner.tf: -------------------------------------------------------------------------------- 1 | resource "block" "volume" { 2 | hoge = 1 3 | foo = 1 4 | } 5 | -------------------------------------------------------------------------------- /src/cli/subcommand.rs: -------------------------------------------------------------------------------- 1 | //! This module defines subcommands of `shisho` command. 2 | 3 | pub mod check; 4 | pub mod completion; 5 | pub mod find; 6 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/encoding/utf_16le.go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/src/cli/tests/ruleset/generic/encoding/utf_16le.go -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/encoding/shift_jis.go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatt-security/shisho/HEAD/src/cli/tests/ruleset/generic/encoding/shift_jis.go -------------------------------------------------------------------------------- /docs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "jsxBracketSameLine": false, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /docs/src/custom/styles/styles.js: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | 3 | const customStyles = css` 4 | 5 | `; 6 | 7 | export const styles = [customStyles]; 8 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | node_modules 3 | *DS_Store 4 | *.env 5 | 6 | .idea/ 7 | public/* 8 | !public/images/ 9 | !public/images/* 10 | !public/favicon.png 11 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/unencrypted_ebs/unmatch.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ebs_volume" "volume" { 2 | availability_zone = "${var.region}a" 3 | size = 1 4 | encrypted = true 5 | } -------------------------------------------------------------------------------- /docs/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/nested_constraints/unmatch.with-inner.tf: -------------------------------------------------------------------------------- 1 | resource "block" "volume" { 2 | hoge = 1 3 | inner { 4 | a = 1 5 | b = 1 6 | } 7 | foo = 1 8 | } 9 | -------------------------------------------------------------------------------- /docs/src/custom-sw-code.js: -------------------------------------------------------------------------------- 1 | workbox.routing.registerRoute( 2 | new RegExp('https:.*min.(css|js)'), 3 | workbox.strategies.staleWhileRevalidate({ 4 | cacheName: 'cdn-cache', 5 | }) 6 | ); 7 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/nested_constraints/match.tf: -------------------------------------------------------------------------------- 1 | resource "block" "volume" { 2 | hoge = 1 3 | inner { 4 | a = 1 5 | test = 1 6 | b = 1 7 | } 8 | foo = 1 9 | } -------------------------------------------------------------------------------- /docs/src/components/mdxComponents/loading.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const LoadingProvider = ({ ...props }) => { 4 | return
; 5 | }; 6 | 7 | export default LoadingProvider; 8 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/encoding/ruleset.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "encoding" 4 | language: go 5 | message: | 6 | testing 7 | pattern: | 8 | fmt.Sprintf("abテストcd") 9 | -------------------------------------------------------------------------------- /src/core/matcher.rs: -------------------------------------------------------------------------------- 1 | mod item; 2 | pub use self::item::*; 3 | 4 | mod literal; 5 | pub use self::literal::*; 6 | 7 | mod state; 8 | pub use self::state::*; 9 | 10 | mod tree; 11 | pub use self::tree::*; 12 | -------------------------------------------------------------------------------- /src/core/ruleset/assets/1.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: rule-01 4 | language: go 5 | message: | 6 | N/A 7 | pattern: | 8 | :[X] && :[X] 9 | rewrite: | 10 | :[X] 11 | -------------------------------------------------------------------------------- /src/core/ruleset/assets/2.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: rule-02 4 | language: go 5 | message: | 6 | N/A 7 | pattern: | 8 | :[X] && :[X] 9 | rewrite: | 10 | :[X] 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bungcip.better-toml", 4 | "vadimcn.vscode-lldb", 5 | "matklad.rust-analyzer", 6 | ], 7 | "unwantedRecommendations": [ 8 | "rust-lang.rust", 9 | ] 10 | } -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | pub mod constraint; 2 | pub mod language; 3 | pub mod matcher; 4 | pub mod node; 5 | pub mod pattern; 6 | pub mod query; 7 | pub mod rewriter; 8 | pub mod ruleset; 9 | pub mod source; 10 | pub mod target; 11 | pub mod tree; 12 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/unencrypted_ebs/match.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ebs_volume" "volume" { 2 | availability_zone = "${var.region}a" 3 | size = 1 4 | } 5 | 6 | resource "aws_ebs_volume" "volume" { 7 | availability_zone = "${var.region}a" 8 | size = 1 9 | } -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | //! This module includes some sub-modules to provide command-line interface of shisho. 2 | 3 | pub mod opts; 4 | pub use self::opts::*; 5 | 6 | pub mod reporter; 7 | pub mod subcommand; 8 | 9 | mod encoding; 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | -------------------------------------------------------------------------------- /docs/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | export const onServiceWorkerUpdateReady = () => { 2 | const answer = window.confirm( 3 | `This tutorial has been updated. ` + 4 | `Reload to display the latest version?` 5 | ) 6 | if (answer === true) { 7 | window.location.reload() 8 | } 9 | } -------------------------------------------------------------------------------- /docs/src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './theme'; 2 | import mdxComponents from './mdxComponents'; 3 | import ThemeProvider from './theme/themeProvider'; 4 | import Layout from './layout'; 5 | import Link from './link'; 6 | 7 | export {mdxComponents, ThemeProvider, Layout, Link} 8 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/uncontrolled_ebs_encryption_key/match.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ebs_volume" "volume" { 2 | availability_zone = "${var.region}a" 3 | size = 1 4 | } 5 | 6 | resource "aws_ebs_volume" "volume" { 7 | availability_zone = "${var.region}a" 8 | size = 1 9 | } 10 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/comment/ruleset.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "comment_test" :[NAME] { 9 | :[...] 10 | // hoge 11 | :[...] 12 | } 13 | -------------------------------------------------------------------------------- /rules/docker.shisho.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: no-latest-tag 4 | language: dockerfile 5 | message: | 6 | `latest` tag is used. Consider to use more concrete tag. 7 | patterns: 8 | - pattern: | 9 | FROM :[_]:latest 10 | - pattern: | 11 | FROM :[_]:latest as :[_] 12 | -------------------------------------------------------------------------------- /docs/src/components/images/closed.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ClosedSvg = () => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default ClosedSvg; 10 | -------------------------------------------------------------------------------- /docs/src/components/images/opened.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const OpenedSvg = () => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default OpenedSvg; 10 | -------------------------------------------------------------------------------- /docs/src/components/search/styles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Algolia } from '@styled-icons/fa-brands/Algolia'; 3 | 4 | export const PoweredBy = () => ( 5 | 6 | Powered by{` `} 7 | 8 | Algolia 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/comment/match.tf: -------------------------------------------------------------------------------- 1 | resource "comment_test" "volume" { 2 | a = 1 3 | // hoge 4 | b = 1 5 | } 6 | 7 | resource "comment_test" "volume" { 8 | a = 1 9 | // hoge 10 | } 11 | 12 | resource "comment_test" "volume" { 13 | // hoge 14 | b = 1 15 | } 16 | 17 | resource "comment_test" "volume" { 18 | // hoge 19 | } 20 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/match-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: match 14 | pattern: attr = 1 15 | -------------------------------------------------------------------------------- /src/core/ruleset/test.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use super::from_path; 4 | 5 | #[test] 6 | fn load() { 7 | let mut ruleset = PathBuf::from(file!()); 8 | ruleset.pop(); 9 | ruleset.push("assets"); 10 | 11 | let ruleset = from_path(ruleset); 12 | assert!(ruleset.is_ok()); 13 | assert_eq!(ruleset.unwrap().len(), 2); 14 | } 15 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/not-match-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-match 14 | pattern: attr = 2 15 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/no-pattern-like.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | # should cause error(s) 12 | constraints: 13 | - target: X 14 | should: match 15 | -------------------------------------------------------------------------------- /docs/src/components/mdxComponents/anchor.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const AnchorTag = ({ children: link, ...props }) => { 4 | if (link) { 5 | return ( 6 | 7 | {link} 8 | 9 | ); 10 | } else { 11 | return null; 12 | } 13 | }; 14 | 15 | export default AnchorTag; 16 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/not-match-regex-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-match 14 | regex-pattern: "^2$" 15 | -------------------------------------------------------------------------------- /docs/src/GithubLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const githubIcon = require('./components/images/github.svg').default; 3 | 4 | const GithubLink = ({ link, text }) => { 5 | return ( 6 | 7 | github 8 | {text} 9 | 10 | ); 11 | }; 12 | 13 | export default GithubLink; 14 | -------------------------------------------------------------------------------- /docs/src/components/link.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as GatsbyLink } from 'gatsby'; 3 | import isAbsoluteUrl from 'is-absolute-url'; 4 | 5 | const Link = ({ to, ...props }) => 6 | isAbsoluteUrl(to) ? ( 7 | 8 | {props.children} 9 | 10 | ) : ( 11 | 12 | ); 13 | 14 | export default Link; 15 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/be-any-of.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | constraints: 12 | - target: X 13 | should: be-any-of 14 | strings: 15 | - "27" 16 | - "1" 17 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/invalid-match-string.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | # should cause error(s) 12 | constraints: 13 | - target: X 14 | should: match 15 | string: "a" 16 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/match-regex-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | :[...] 10 | attr = :[X] 11 | :[...] 12 | } 13 | constraints: 14 | - target: X 15 | should: match 16 | regex-pattern: "^1$" 17 | -------------------------------------------------------------------------------- /docs/src/components/mdxComponents/LiveProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'; 3 | 4 | const ReactLiveProvider = ({ code }) => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default ReactLiveProvider; 15 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/match-any-of-regex-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | constraints: 12 | - target: X 13 | should: match-any-of 14 | regex-patterns: 15 | - "^3$" 16 | - "^1$" 17 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/not-be-any-of.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-be-any-of 14 | strings: 15 | - "27" 16 | - "32" 17 | - "2" 18 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/match-any-of-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: match-any-of 14 | patterns: 15 | - pattern: attr = 3 16 | - pattern: attr = 1 17 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/not-match-any-of-regex-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-match-any-of 14 | regex-patterns: 15 | - "^2$" 16 | - "^33$" 17 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/shared_constraints/dockerfile: -------------------------------------------------------------------------------- 1 | FROM test 2 | FROM test as alias1 3 | FROM foo 4 | FROM foo as alias2 5 | 6 | FROM test:tag 7 | FROM test:tag as alias3 8 | FROM foo:tag 9 | FROM foo:tag as alias4 10 | 11 | FROM test@hash 12 | FROM test@hash as alias5 13 | FROM foo@hash 14 | FROM foo@hash as alias6 15 | 16 | FROM test:tag@hash 17 | FROM test:tag@hash as alias7 18 | FROM foo:tag@hash 19 | FROM foo:tag@hash as alias8 -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/invalid-match-strings.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | # should cause error(s) 12 | constraints: 13 | - target: X 14 | should: match 15 | strings: 16 | - "27" 17 | - "1" 18 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/shared_constraints/Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM test 2 | FROM test as alias1 3 | FROM foo 4 | FROM foo as alias2 5 | 6 | FROM test:tag 7 | FROM test:tag as alias3 8 | FROM foo:tag 9 | FROM foo:tag as alias4 10 | 11 | FROM test@hash 12 | FROM test@hash as alias5 13 | FROM foo@hash 14 | FROM foo@hash as alias6 15 | 16 | FROM test:tag@hash 17 | FROM test:tag@hash as alias7 18 | FROM foo:tag@hash 19 | FROM foo:tag@hash as alias8 -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/shared_constraints/test.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM test 2 | FROM test as alias1 3 | FROM foo 4 | FROM foo as alias2 5 | 6 | FROM test:tag 7 | FROM test:tag as alias3 8 | FROM foo:tag 9 | FROM foo:tag as alias4 10 | 11 | FROM test@hash 12 | FROM test@hash as alias5 13 | FROM foo@hash 14 | FROM foo@hash as alias6 15 | 16 | FROM test:tag@hash 17 | FROM test:tag@hash as alias7 18 | FROM foo:tag@hash 19 | FROM foo:tag@hash as alias8 -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | ruleset_test! { 4 | unencrypted_ebs: [("ruleset.yaml", "match.tf", Ok(2), None), ("ruleset.yaml", "unmatch.tf", Ok(0), None)], 5 | uncontrolled_ebs_encryption_key: [("ruleset.yaml", "match.tf", Ok(2), None), ("ruleset.yaml", "unmatch.tf", Ok(0), None)], 6 | comment: [("ruleset.yaml", "match.tf", Ok(4), None), ("ruleset.yaml", "unmatch.tf", Ok(0), None)], 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/ambiguous-pattern-use.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | # should cause error(s) 12 | constraints: 13 | - target: X 14 | should: match 15 | pattern: "a" 16 | patterns: 17 | - pattern: "a" 18 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/mixed-pattern-like.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | # should cause error(s) 12 | constraints: 13 | - target: X 14 | should: match 15 | regex-pattern: "a" 16 | patterns: 17 | - pattern: "a" 18 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/invalid_constraints/ambiguous-regex-pattern-use.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | attr = :[X] 10 | } 11 | # should cause error(s) 12 | constraints: 13 | - target: X 14 | should: match 15 | regex-pattern: "a" 16 | regex-patterns: 17 | - "a" 18 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/uncontrolled_ebs_encryption_key/unmatch.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ebs_volume" "volume" { 2 | availability_zone = "${var.region}a" 3 | size = 1 4 | kms_key_id = aws_kms_key.ebs_encryption.arn 5 | } 6 | 7 | resource "aws_ebs_volume" "volume" { 8 | availability_zone = "${var.region}a" 9 | size = 1 10 | kms_key_id = aws_kms_key.ebs_encryption.arn 11 | } 12 | 13 | resource "aws_kms_key" "ebs_encryption" { 14 | enable_key_rotation = true 15 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'type:feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Problem 11 | 12 | TODO: Please describe the problem you have. 13 | 14 | # Possible Solutions 15 | 16 | TODO: Please explain the possible solutions you've considered if possible. 17 | 18 | # Additional Notes 19 | 20 | TODO: Describe any other information you'd like to mention. -------------------------------------------------------------------------------- /docs/content/roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Roadmap' 3 | metaTitle: 'Roadmap' 4 | metaDescription: 'Roadmap' 5 | --- 6 | 7 | We are continuously working hard on the improvement of our services. New features that we are currently developing are: 8 | 9 | ## Shisho - Static Code Analyzer 10 | 11 | #### IaC Support 12 | - [AWS CloudFormation](https://aws.amazon.com/cloudformation/) 13 | - [Azure Resource Manager](https://azure.microsoft.com/en-us/features/resource-manager/) 14 | 15 | > Last Update: 01/12/2022 -------------------------------------------------------------------------------- /docs/src/components/search/hitComps.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Highlight, Snippet } from 'react-instantsearch-dom'; 3 | import { Link } from 'gatsby'; 4 | 5 | export const PageHit = clickHandler => ({ hit }) => ( 6 |
7 | 8 |
9 | 10 |
11 | 12 | 13 |
14 | ); 15 | -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:buster 2 | 3 | # Create app directory 4 | WORKDIR /app 5 | 6 | # Install app dependencies 7 | # RUN npm -g install serve 8 | RUN npm -g install gatsby-cli 9 | 10 | COPY package*.json ./ 11 | 12 | RUN npm ci 13 | 14 | # Bundle app source 15 | COPY . . 16 | 17 | # Build static files 18 | RUN npm run build 19 | 20 | # serve on port 8080 21 | # CMD ["serve", "-l", "tcp://0.0.0.0:8080", "public"] 22 | CMD ["gatsby", "serve", "--verbose", "--prefix-paths", "-p", "8080", "--host", "0.0.0.0"] 23 | -------------------------------------------------------------------------------- /docs/src/YoutubeEmbed.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const YoutubeEmbed = ({ link }) => { 4 | return ( 5 |
6 | 15 |
16 | ); 17 | }; 18 | 19 | export default YoutubeEmbed; 20 | -------------------------------------------------------------------------------- /docs/src/components/themeProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ThemeProvider as EmotionThemeProvider } from '@emotion/react'; 3 | import { default as defaultTheme } from './theme'; 4 | import Header from './Header'; 5 | 6 | export default function ThemeProvider({ children, theme = {}, location }) { 7 | return ( 8 |
9 |
10 | {children} 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use shisho::cli; 2 | use structopt::StructOpt; 3 | 4 | /// `main` is an entrypoint of shisho. 5 | fn main() { 6 | let opts: cli::Opts = cli::Opts::from_args(); 7 | 8 | let exit_code = match opts.sub_command { 9 | cli::SubCommand::Completion(opts) => cli::subcommand::completion::run(opts), 10 | cli::SubCommand::Check(opts) => cli::subcommand::check::run(opts), 11 | cli::SubCommand::Find(opts) => cli::subcommand::find::run(opts), 12 | }; 13 | 14 | std::process::exit(exit_code) 15 | } 16 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/constraints/not-match-any-of-pattern.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-match-any-of 14 | patterns: 15 | - pattern: attr = 32 16 | - pattern: attr = 2 17 | - pattern: | 18 | inner { 19 | :[...] 20 | } 21 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/unencrypted_ebs/ruleset.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "unencrypted-ebs-volume" 4 | language: hcl 5 | message: | 6 | There was unencrypted EBS module. 7 | pattern: | 8 | resource "aws_ebs_volume" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-match 14 | pattern: | 15 | encrypted = true 16 | rewrite: | 17 | resource "aws_ebs_volume" :[NAME] { 18 | :[X] 19 | encrypted = true 20 | } 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:latest AS builder 2 | RUN rustup target add x86_64-unknown-linux-gnu 3 | RUN rustup component add rustfmt 4 | WORKDIR /build 5 | 6 | # cache dependencies 7 | COPY ./Cargo.lock ./Cargo.lock 8 | COPY ./Cargo.toml ./Cargo.toml 9 | COPY ./third_party ./third_party 10 | RUN mkdir src && \ 11 | echo "fn main() {}" > src/main.rs && \ 12 | cargo build --release 13 | 14 | # build 15 | COPY ./ . 16 | RUN cargo build --release 17 | 18 | FROM gcr.io/distroless/cc:latest 19 | WORKDIR /workspace 20 | COPY --from=builder /build/target/release/shisho /shisho 21 | ENTRYPOINT ["/shisho"] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: '' 5 | labels: 'type:bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Description of the bug 11 | 12 | TODO: Please provide a clear and concise description of the bug you found. 13 | 14 | # Steps to Reproduce 15 | 16 | TODO: Please describe steps to reproduce the bug. 17 | 18 | # Expected Behaviour 19 | 20 | TODO: Please describe the behavior you expect with the aforementioned steps. 21 | 22 | # Additional Materials 23 | 24 | TODO: Please add screenshots to help explain the bug if possible. 25 | 26 | -------------------------------------------------------------------------------- /docs/src/components/images/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/src/components/theme/index.js: -------------------------------------------------------------------------------- 1 | const baseTheme = { 2 | fonts: { 3 | mono: '"SF Mono", "Roboto Mono", Menlo, monospace', 4 | }, 5 | }; 6 | 7 | const lightTheme = { 8 | ...baseTheme, 9 | colors: { 10 | background: '#fff', 11 | heading: '#000', 12 | text: '#3B454E', 13 | preFormattedText: 'rgb(245, 247, 249)', 14 | link: '#1000EE', 15 | }, 16 | }; 17 | 18 | const darkTheme = { 19 | ...baseTheme, 20 | colors: { 21 | background: '#001933', 22 | heading: '#fff', 23 | text: '#fff', 24 | preFormattedText: '#000', 25 | link: '#1ED3C6', 26 | }, 27 | }; 28 | 29 | export { lightTheme, darkTheme }; 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | TODO: Describe why you've made your PR and what you've done in the PR. You can put a link for related GitHub issues here. 4 | 5 | # Checklist 6 | 7 | - [ ] I opened a draft PR or added the `[WIP]` to the title if my PR is not ready for review. 8 | - [ ] I have reviewed the code by myself. 9 | - [ ] I have commented my code, particularly in hard-to-understand areas. 10 | - [ ] I have made corresponding changes to the documentation. 11 | - [ ] I have added tests enough to show how your code behaves and that your code works as expected. 12 | 13 | # Additional Notes 14 | 15 | TODO: Describe any other information you'd like to mention. 16 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/nested_constraints/ruleset.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "test" 4 | language: hcl 5 | message: | 6 | test 7 | pattern: | 8 | resource "block" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: match 14 | pattern: | 15 | inner { 16 | :[...Z] 17 | } 18 | constraints: 19 | - target: Z 20 | should: match 21 | pattern: | 22 | test = :[HOO] 23 | rewrite_options: 24 | - | 25 | resource "block" :[NAME] { 26 | test = :[HOO] 27 | } 28 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/shared_constraints/ruleset.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "use-trusted-base-images" 4 | language: dockerfile 5 | message: | 6 | Use trusted base images if possible. 7 | patterns: 8 | - pattern: FROM :[NAME] 9 | - pattern: FROM :[NAME] AS :[ALIAS] 10 | - pattern: FROM :[NAME]@:[HASH] 11 | - pattern: FROM :[NAME]@:[HASH] AS :[ALIAS] 12 | - pattern: FROM :[NAME]::[TAG] 13 | - pattern: FROM :[NAME]::[TAG] AS :[ALIAS] 14 | - pattern: FROM :[NAME]::[TAG]@:[HASH] 15 | - pattern: FROM :[NAME]::[TAG]@:[HASH] AS :[ALIAS] 16 | constraints: 17 | - target: NAME 18 | should: be-any-of 19 | strings: 20 | - test 21 | -------------------------------------------------------------------------------- /docs/content/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Welcome' 3 | metaTitle: 'Welcome to Shisho Documentation' 4 | --- 5 | 6 | # Welcome to Shisho Documentation 7 | 8 | Shisho Documentation has mainly four components and the following pages explain for further details. 9 | 10 | - ["Shisho"](/shisho) is a lightweight static code analyzer and this is the core engine for the Shisho product family. 11 | - ["Shisho Cloud"](/shisho-cloud) is a cloud-based service for IaC security. 12 | - ["Shisho Action"](/shisho-action) is an extension of [GitHub Actions](https://github.com/features/actions) to execute the code analysis with Shisho. 13 | - ["Shisho Dojo"](/shisho-dojo) shows IaC code examples in [GitHub](https://github.com/), the utilization of resources and parameters, and security best practices. -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Check test coverage 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "docs" 7 | - ".vscode" 8 | 9 | jobs: 10 | coverage: 11 | name: coverage 12 | runs-on: ubuntu-latest 13 | container: 14 | image: xd009642/tarpaulin:develop-nightly 15 | options: --security-opt seccomp=unconfined 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | 22 | - name: Generate code coverage 23 | run: | 24 | cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml 25 | 26 | - name: Upload to codecov.io 27 | uses: codecov/codecov-action@v1 28 | with: 29 | fail_ci_if_error: true 30 | -------------------------------------------------------------------------------- /src/cli/subcommand/completion.rs: -------------------------------------------------------------------------------- 1 | //! This module defines `completion` subcommand. 2 | 3 | use std::io; 4 | use structopt::clap::Shell; 5 | use structopt::StructOpt; 6 | 7 | /// Prints scripts for shell completion. 8 | #[derive(StructOpt, Debug)] 9 | pub enum CompletionOpts { 10 | /// For zsh 11 | Zsh, 12 | /// For bash 13 | Bash, 14 | /// For fish 15 | Fish, 16 | } 17 | 18 | pub fn run(opts: CompletionOpts) -> i32 { 19 | match opts { 20 | CompletionOpts::Bash => completion(Shell::Bash), 21 | CompletionOpts::Zsh => completion(Shell::Zsh), 22 | CompletionOpts::Fish => completion(Shell::Fish), 23 | }; 24 | 25 | 0 26 | } 27 | 28 | fn completion(s: Shell) { 29 | super::super::Opts::clap().gen_completions_to(env!("CARGO_PKG_NAME"), s, &mut io::stdout()) 30 | } 31 | -------------------------------------------------------------------------------- /docs/public/images/twitter-brands-block.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/src/components/images/twitter-brands-block.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/content/shisho-action.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Shisho Action' 3 | metaTitle: 'Shisho Action Documentation' 4 | metaDescription: 'This page describes details of Shisho Action.' 5 | --- 6 | 7 | ## Introduction 8 | 9 | **Shisho Action** executes your own Shisho rule set during the workflow of [GitHub Actions](https://github.com/features/actions). This extension is officially provided by GitHub Marketplace. 10 | 11 | - [GitHub Repository](https://github.com/flatt-security/shisho-action) 12 | - [GitHub Marketplace](https://github.com/marketplace/actions/shisho-action) 13 | 14 | ![demo](https://github.com/flatt-security/shisho-action/raw/main/docs/images/action.png) 15 | 16 | ## Feedback 17 | 18 | We'd love to hear your feedback! Feel free to ask Shisho team anything at [GitHub issues](https://github.com/flatt-security/shisho-action/issues). 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/cli/opts.rs: -------------------------------------------------------------------------------- 1 | //! This module defines options of `shisho` command. 2 | 3 | use super::subcommand::*; 4 | use crate::cli::reporter::ReporterType; 5 | use clap_verbosity_flag::Verbosity; 6 | use structopt::StructOpt; 7 | 8 | #[derive(StructOpt, Debug)] 9 | pub struct Opts { 10 | #[structopt(subcommand)] 11 | pub sub_command: SubCommand, 12 | } 13 | 14 | #[derive(StructOpt, Debug)] 15 | pub struct CommonOpts { 16 | #[structopt(flatten)] 17 | pub verbose: Verbosity, 18 | } 19 | 20 | #[derive(StructOpt, Debug)] 21 | pub struct ReportOpts { 22 | #[structopt(long, default_value = "console", possible_values(&ReporterType::variants()))] 23 | pub format: ReporterType, 24 | } 25 | 26 | #[derive(StructOpt, Debug)] 27 | pub enum SubCommand { 28 | Completion(completion::CompletionOpts), 29 | Find(find::FindOpts), 30 | Check(check::CheckOpts), 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Debug executable 'shisho'", 8 | "cargo": { 9 | "args": ["build", "--bin=shisho", "--package=shisho"], 10 | "filter": { 11 | "name": "shisho", 12 | "kind": "bin" 13 | } 14 | }, 15 | "args": ["open"], 16 | "cwd": "${workspaceFolder}" 17 | }, 18 | { 19 | "type": "lldb", 20 | "request": "launch", 21 | "name": "Debug unit tests in executable 'shisho'", 22 | "cargo": { 23 | "args": ["test", "--no-run", "--bin=shisho", "--package=shisho"], 24 | "filter": { 25 | "name": "shisho", 26 | "kind": "bin" 27 | } 28 | }, 29 | "args": ["open"], 30 | "cwd": "${workspaceFolder}" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /docs/content/shisho/learn-shisho.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Learn' 3 | metaTitle: 'Learn Shisho' 4 | metaDescription: 'This page describes details of Shisho.' 5 | --- 6 | 7 | Shisho has two major features: (1) pattern matching for code and (2) transformation of the matched code. To use them, you need to write _a rule_, including _patterns_, _rule constraints_, and _rewrite patterns_. The following page explains these concepts with tutorials: 8 | 9 | - ["Pattern"](/shisho/learn-shisho/01-pattern), which describes the code to search. 10 | - ["Rule and Rule Set"](/shisho/learn-shisho/02-rule), which bundles miscellaneous information on pattern matching and code transformation. 11 | - ["Rule Constraint"](/shisho/learn-shisho/03-constraint), which imposes additional constraints on pattern matching. 12 | - ["Rewrite Option(s)"](/shisho/learn-shisho/04-rewrite-option), which describes how the matched parts of the code should be rewritten. 13 | -------------------------------------------------------------------------------- /src/core/rewriter/literal.rs: -------------------------------------------------------------------------------- 1 | use crate::core::language::Queryable; 2 | use crate::core::node::Node; 3 | use crate::core::rewriter::builder::{Segment, SnippetBuilder}; 4 | use anyhow::Result; 5 | use regex::Captures; 6 | 7 | impl<'tree, T: Queryable> SnippetBuilder<'tree, T> { 8 | pub(crate) fn from_string_leaf(&self, node: &Node) -> Result { 9 | let body = node.as_str().to_string(); 10 | let r = regex::Regex::new(r":\[(\.\.\.)?(?P[A-Z_][A-Z_0-9]*)\]").unwrap(); 11 | let body = r.replace_all(body.as_str(), |caps: &Captures| { 12 | let name = caps.name("name").unwrap().as_str(); 13 | self.from_metavariable(node, name) 14 | .map(|x| x.body) 15 | .unwrap_or_default() 16 | }); 17 | Ok(Segment { 18 | body: body.into(), 19 | start_byte: node.start_byte(), 20 | end_byte: node.end_byte(), 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/tree-sitter-hcl"] 2 | path = third_party/tree-sitter-hcl 3 | url = https://github.com/flatt-security/tree-sitter-hcl 4 | [submodule "third_party/tree-sitter-hcl-query"] 5 | path = third_party/tree-sitter-hcl-query 6 | url = https://github.com/flatt-security/tree-sitter-hcl-query 7 | [submodule "third_party/tree-sitter-go"] 8 | path = third_party/tree-sitter-go 9 | url = https://github.com/tree-sitter/tree-sitter-go 10 | [submodule "third_party/tree-sitter-go-query"] 11 | path = third_party/tree-sitter-go-query 12 | url = https://github.com/flatt-security/tree-sitter-go-query 13 | [submodule "third_party/tree-sitter-dockerfile-query"] 14 | path = third_party/tree-sitter-dockerfile-query 15 | url = https://github.com/flatt-security/tree-sitter-dockerfile-query.git 16 | [submodule "third_party/tree-sitter-dockerfile"] 17 | path = third_party/tree-sitter-dockerfile 18 | url = https://github.com/flatt-security/tree-sitter-dockerfile.git 19 | -------------------------------------------------------------------------------- /docs/src/utils/algolia.js: -------------------------------------------------------------------------------- 1 | const config = require('../../config.js'); 2 | 3 | const pageQuery = `{ 4 | pages: allMdx { 5 | edges { 6 | node { 7 | objectID: id 8 | fields { 9 | slug 10 | } 11 | headings { 12 | value 13 | } 14 | frontmatter { 15 | title 16 | metaDescription 17 | } 18 | excerpt(pruneLength: 50000) 19 | } 20 | } 21 | } 22 | }`; 23 | 24 | const flatten = arr => 25 | arr.map(({ node: { frontmatter, fields, ...rest } }) => ({ 26 | ...frontmatter, 27 | ...fields, 28 | ...rest, 29 | })); 30 | 31 | const settings = { attributesToSnippet: [`excerpt:20`] }; 32 | 33 | const indexName = config.header.search ? config.header.search.indexName : ''; 34 | 35 | const queries = [ 36 | { 37 | query: pageQuery, 38 | transformer: ({ data }) => flatten(data.pages.edges), 39 | indexName: `${indexName}`, 40 | settings, 41 | }, 42 | ]; 43 | 44 | module.exports = queries; 45 | -------------------------------------------------------------------------------- /docs/src/components/images/discord-brands-block.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/src/custom/config/codeBlockLanguages.js: -------------------------------------------------------------------------------- 1 | export function applyLanguages(_Prism) { 2 | /** 3 | * Here you have the possibility to add other supported languages to Prism and thus to the code highlighting. 4 | * 5 | * We support the following languages automatically: 6 | * markup, bash, clike, c, cpp, css, javascript, jsx, coffeescript, actionscript, css-extr, 7 | * diff, git, go, graphql, handlebars, json, less, makefile, markdown, objectivec, ocaml, python, 8 | * reason, sass, scss, sql, stylus, tsx, typescript, wasm, yaml 9 | * 10 | * If you need more languages you can add them yourself as follows: 11 | * (https://github.com/PrismJS/prism/tree/master/components) 12 | * 13 | * ```js 14 | * require("prismjs/components/prism-docker"); 15 | * require("prismjs/components/prism-ada"); 16 | * ``` 17 | */ 18 | 19 | } 20 | 21 | export function getTheme(_Prism) { 22 | /** 23 | * Here you have the possibility to change the prism highlighting. 24 | */ 25 | return require('prism-react-renderer/themes/vsDark').default; 26 | } 27 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/hcl/uncontrolled_ebs_encryption_key/ruleset.yaml: -------------------------------------------------------------------------------- 1 | version: "1" 2 | rules: 3 | - id: "uncontrolled-ebs-encryption-key" 4 | language: hcl 5 | message: | 6 | To increase control of the encryption of EBS volume and manage factors like rotation, you can use customer managed keys. 7 | pattern: | 8 | resource "aws_ebs_volume" :[NAME] { 9 | :[...X] 10 | } 11 | constraints: 12 | - target: X 13 | should: not-match 14 | pattern: | 15 | kms_key_id = :[_] 16 | rewrite_options: 17 | - | 18 | resource "aws_kms_key" "ebs_encryption" { 19 | enable_key_rotation = true 20 | } 21 | 22 | resource "aws_ebs_volume" :[NAME] { 23 | :[X] 24 | kms_key_id = aws_kms_key.ebs_encryption.arn 25 | } 26 | - | 27 | resource "aws_kms_key" "ebs_encryption" { 28 | } 29 | 30 | resource "aws_ebs_volume" :[NAME] { 31 | :[X] 32 | kms_key_id = aws_kms_key.ebs_encryption.arn 33 | } 34 | -------------------------------------------------------------------------------- /src/core/rewriter.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod literal; 3 | 4 | use crate::core::{language::Queryable, matcher::MatchedItem, node::RootNode, pattern::Pattern}; 5 | use anyhow::Result; 6 | 7 | use self::builder::SnippetBuilder; 8 | 9 | pub struct RewriteOption<'a, T> 10 | where 11 | T: Queryable, 12 | { 13 | pub root_node: RootNode<'a>, 14 | pattern: &'a Pattern, 15 | } 16 | 17 | impl<'a, T> RewriteOption<'a, T> 18 | where 19 | T: Queryable, 20 | { 21 | pub fn to_rewritten_snippet<'tree>(&self, item: &'tree MatchedItem) -> Result { 22 | Ok(SnippetBuilder::new(self, item) 23 | .from_root(&self.root_node)? 24 | .body) 25 | } 26 | } 27 | 28 | impl<'a, T> From<&'a Pattern> for RewriteOption<'a, T> 29 | where 30 | T: Queryable, 31 | { 32 | fn from(pattern: &'a Pattern) -> Self { 33 | let root_node = pattern.to_root_node(); 34 | Self { pattern, root_node } 35 | } 36 | } 37 | 38 | impl Pattern 39 | where 40 | T: Queryable, 41 | { 42 | pub fn as_rewrite_option(&'_ self) -> RewriteOption<'_, T> { 43 | self.into() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/src/components/images/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /docs/content/shisho-dojo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Shisho Dojo' 3 | metaTitle: 'Shisho Dojo Documentation' 4 | metaDescription: 'This page describes details of Shisho Dojo.' 5 | --- 6 | 7 | ## Introduction 8 | 9 | **[Shisho Dojo](https://shisho.dev/dojo/)** indicates actual code examples in [GitHub](https://github.com/), the utilization of resources and parameters, and security best practices. 10 | 11 | ![demo](https://shisho.dev/dojo/images/site-preview.png) 12 | 13 | ## Supported Services 14 | 15 | 1. [Terrafrom](https://www.terraform.io/) 16 | - [Amazon Web Service (AWS)](https://aws.amazon.com/) 17 | - [Google Cloud Platform (GCP)](https://cloud.google.com/) 18 | - [Microsoft Azure](https://azure.microsoft.com/) 19 | 2. [AWS CloudFormation](https://aws.amazon.com/cloudformation/) 20 | 3. [Azure Resource Manager](https://azure.microsoft.com/en-us/features/resource-manager/) 21 | 22 | ## Feedback 23 | 24 | We'd love to hear your feedback! If you find any errors or want to give us your suggestions or requests, please send your feedback from the [Shisho Dojo Contact Form](https://docs.google.com/forms/d/e/1FAIpQLSeHEvc9-eHpWIEHj8onyUAXqpgVQRpQ3w1Ys45fU6FjL5XumQ/viewform). 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/src/components/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /docs/content/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Changelog' 3 | metaTitle: 'Changelog' 4 | metaDescription: 'Changelog' 5 | --- 6 | 7 | #### Shisho Docs - 01/28/2022 8 | 9 | We added some explanations about [Shisho Cloud](https://shisho.dev/). 10 | 11 | #### Shisho Cloud - 01/28/2022 12 | 13 | We released a new registration wizard to improve the UX. 14 | 15 | #### Shisho LP - 01/26/2022 16 | 17 | We released "[About Us](https://shisho.dev/about/)" page. Hello, everyone ! :) 18 | 19 | about us screenshot 20 | 21 | #### Shisho LP - 01/21/2022 22 | 23 | We updated some content with a new design. 24 | 25 | #### Shisho Cloud - 01/21/2022 26 | 27 | We released the new console UI. 28 | 29 | #### Shisho Cloud - 01/18/2022 30 | 31 | We released PR comment functions for [GitLab](https://about.gitlab.com/) and [BitBucket](https://bitbucket.org/product). 32 | 33 | #### Shisho Cloud - 01/14/2022 34 | 35 | We added new rules for [AWS](https://aws.amazon.com/), [GCP](https://cloud.google.com/) and [Azure](https://azure.microsoft.com/). 36 | 37 | #### Shisho Dojo - 12/18/2021 38 | 39 | We launched [SHisho Dojo](https://shisho.dev/dojo/). 40 | 41 | > Last Update: 01/28/2022 -------------------------------------------------------------------------------- /docs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:react/recommended", 6 | "plugin:jsx-a11y/recommended", 7 | "prettier", 8 | "prettier/react" 9 | ], 10 | "plugins": ["react", "import", "jsx-a11y"], 11 | "settings": { 12 | "react": { 13 | "version": "detect" 14 | } 15 | }, 16 | "rules": { 17 | "react/prop-types": 0, 18 | "react/react-in-jsx-scope": "off", 19 | "lines-between-class-members": ["error", "always"], 20 | "padding-line-between-statements": [ 21 | "error", 22 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, 23 | { 24 | "blankLine": "always", 25 | "prev": ["const", "let", "var"], 26 | "next": ["const", "let", "var"] 27 | }, 28 | { "blankLine": "always", "prev": "directive", "next": "*" }, 29 | { "blankLine": "any", "prev": "directive", "next": "directive" } 30 | ] 31 | }, 32 | "parser": "babel-eslint", 33 | "parserOptions": { 34 | "ecmaVersion": 10, 35 | "sourceType": "module", 36 | "ecmaFeatures": { 37 | "jsx": true 38 | } 39 | }, 40 | "env": { 41 | "es6": true, 42 | "browser": true, 43 | "node": true 44 | }, 45 | "globals": { 46 | "graphql": false 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/cli/reporter.rs: -------------------------------------------------------------------------------- 1 | mod console; 2 | pub use self::console::*; 3 | 4 | mod json; 5 | pub use self::json::*; 6 | 7 | mod sarif; 8 | pub use self::sarif::*; 9 | 10 | use crate::core::{language::Queryable, matcher::MatchedItem, ruleset::Rule, target::Target}; 11 | use anyhow::Result; 12 | use std::str::FromStr; 13 | 14 | pub trait Reporter<'a> { 15 | type Writer: std::io::Write; 16 | fn new(writer: &'a mut Self::Writer) -> Self 17 | where 18 | Self: Sized; 19 | 20 | fn add_entry( 21 | &mut self, 22 | target: &Target, 23 | items: Vec<(&Rule, MatchedItem)>, 24 | ) -> Result<()>; 25 | 26 | fn report(&mut self) -> Result<()>; 27 | } 28 | 29 | #[derive(Debug)] 30 | pub enum ReporterType { 31 | JSON, 32 | Console, 33 | SARIF, 34 | } 35 | 36 | impl FromStr for ReporterType { 37 | type Err = String; 38 | 39 | fn from_str(input: &str) -> Result { 40 | match input { 41 | "json" => Ok(ReporterType::JSON), 42 | "console" => Ok(ReporterType::Console), 43 | "sarif" => Ok(ReporterType::SARIF), 44 | _ => Err("".into()), 45 | } 46 | } 47 | } 48 | 49 | impl ReporterType { 50 | pub fn variants() -> [&'static str; 3] { 51 | ["json", "console", "sarif"] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/core/query.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{language::Queryable, node::RootNode, pattern::Pattern}; 2 | 3 | use super::{constraint::Constraint, pattern::PatternWithConstraints}; 4 | 5 | #[derive(Debug)] 6 | pub struct QueryPattern<'a, T> 7 | where 8 | T: Queryable, 9 | { 10 | pub root_node: RootNode<'a>, 11 | pattern: &'a Pattern, 12 | } 13 | 14 | #[derive(Debug, Hash, PartialEq, Eq, Clone)] 15 | pub struct MetavariableId(pub String); 16 | 17 | impl<'a, T> From<&'a Pattern> for QueryPattern<'a, T> 18 | where 19 | T: Queryable, 20 | { 21 | fn from(pattern: &'a Pattern) -> Self { 22 | let root_node = pattern.to_root_node(); 23 | QueryPattern { root_node, pattern } 24 | } 25 | } 26 | 27 | pub struct Query<'a, T: Queryable> { 28 | pub pattern: QueryPattern<'a, T>, 29 | pub constraints: &'a Vec>, 30 | } 31 | 32 | impl<'a, T> From<&'a PatternWithConstraints> for Query<'a, T> 33 | where 34 | T: Queryable, 35 | { 36 | fn from(pc: &'a PatternWithConstraints) -> Self { 37 | Self { 38 | pattern: (&pc.pattern).into(), 39 | constraints: &pc.constraints, 40 | } 41 | } 42 | } 43 | 44 | impl PatternWithConstraints 45 | where 46 | T: Queryable, 47 | { 48 | pub fn as_query(&'_ self) -> Query<'_, T> { 49 | self.into() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shisho" 3 | version = "0.5.2" 4 | authors = ["Flatt Security Inc."] 5 | edition = "2018" 6 | license = "AGPL-3.0-only" 7 | 8 | [[bin]] 9 | name = "shisho" 10 | test = false 11 | bench = false 12 | 13 | [dependencies] 14 | log = "0.4" 15 | clap-verbosity-flag = "0.3.1" 16 | structopt = "0.3.21" 17 | anyhow = "1.0" 18 | thiserror = "1.0" 19 | tree-sitter = "0.19.5" 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_yaml = "0.8" 22 | serde_json = "1.0.67" 23 | regex = "1.5.5" 24 | ansi_term = "0.12.1" 25 | similar = { version = "2.0.0", features = ["text", "inline", "bytes"] } 26 | walkdir = "2" 27 | encoding_rs = "0.8.28" 28 | encoding_rs_io = "0.1.7" 29 | serde-sarif = "0.2.17" 30 | itertools = "0.10.1" 31 | pathdiff = "0.2.1" 32 | glob = "0.3.0" 33 | 34 | [dependencies.tree-sitter-hcl] 35 | path = "./third_party/tree-sitter-hcl" 36 | 37 | [dependencies.tree-sitter-hcl-query] 38 | path = "./third_party/tree-sitter-hcl-query" 39 | 40 | [dependencies.tree-sitter-go] 41 | path = "./third_party/tree-sitter-go" 42 | 43 | [dependencies.tree-sitter-go-query] 44 | path = "./third_party/tree-sitter-go-query" 45 | 46 | [dependencies.tree-sitter-dockerfile] 47 | path = "./third_party/tree-sitter-dockerfile" 48 | 49 | [dependencies.tree-sitter-dockerfile-query] 50 | path = "./third_party/tree-sitter-dockerfile-query" 51 | -------------------------------------------------------------------------------- /docs/content/shisho-cloud.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Shisho Cloud' 3 | metaTitle: 'Shisho Cloud Documentation' 4 | metaDescription: 'This page describes details of Shisho Cloud.' 5 | --- 6 | 7 | ## Introduction 8 | 9 | **[Shisho Cloud](https://shisho.dev/)**, which is a SaaS-based service, supports you in fixing security matters in your infrastructure as code with auto-generated patches. This is developer-friendly and provides seamless integration with multiple code repository managers. This section explains further details of the utilization, benefits and frequently asked questions, etc. 10 | 11 | ![demo](https://storage.googleapis.com/studio-design-asset-files/projects/1Va6K5jKW7/s-1270x760_0a90b16b-7f1b-41b3-9db3-d1e5b3d909c9.gif) 12 | 13 | ## Registraion 14 | 15 | Please check the page [Getting Started](/shisho-cloud/getting-started). This is easy enough! 16 | 17 | ## Supported Repository Managers 18 | 19 | 1. [GitHub](https://github.com/) 20 | 2. [GitLab](https://about.gitlab.com/) 21 | 3. [BitBucket](https://bitbucket.org/product) 22 | 23 | ## Supported Cloud Services 24 | 25 | Shisho Cloud currently supports below cloud services in [Terrafrom](https://www.terraform.io/) code. 26 | 27 | - [Amazon Web Service (AWS)](https://aws.amazon.com/) 28 | - [Google Cloud Platform (GCP)](https://cloud.google.com/) 29 | - [Microsoft Azure](https://azure.microsoft.com/) 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | For `content` directories: 2 | 3 | MIT License 4 | 5 | Copyright (c) 2021 Flatt Security Inc. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | Files other than `content` directory are under MIT License. 26 | See https://github.com/hasura/gatsby-gitbook-starter/blob/master/LICENSE for additional details. 27 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Public Documentations for Shisho 2 | 3 | This directory contains public documentations for Shisho. 4 | 5 | ## How to add/edit a page 6 | 7 | ### File Location 8 | 9 | You can add/edit a page by adding/changing Markdown files (`.mdx` or `.md`) in `./content` directory. 10 | 11 | ### Page Metadata 12 | 13 | As you can see in some docs under `./content`, each document includes the following properties: 14 | 15 | - `title`: the value of `` element 16 | - `metaTitle`: the value of `<meta name="title" ...>` and `<meta name="og:title" ...>` 17 | - `metaDescription`: the value of `<meta name="description" ...>` and `<meta name="og:description" ...>` 18 | 19 | These properties are usually configured in the top of each document like the following example: 20 | 21 | ```md 22 | --- 23 | title: 'Rewrite Pattern' 24 | metaTitle: 'Rewrite Pattern' 25 | metaDescription: 'This page describes details of rewrite patterns for pattern matching.' 26 | --- 27 | 28 | the content of document comes here 29 | ``` 30 | 31 | ## How to see Web docs (docs.shisho.dev) locally 32 | 33 | Run the following command(s): 34 | 35 | ```sh 36 | # in this directory 37 | yarn install 38 | yarn start 39 | ``` 40 | 41 | ## How to build Web docs locally 42 | 43 | Run the following command(s): 44 | 45 | ```sh 46 | # in this directory 47 | yarn install 48 | yarn build 49 | ``` 50 | 51 | ## How to deploy Web docs to `docs.shisho.dev` 52 | 53 | A push event for `main` branch triggers the deployment for `docs.shisho.dev`. -------------------------------------------------------------------------------- /docs/src/components/images/logo.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" 4 | viewBox="0 0 85 95" style="enable-background:new 0 0 85 95;" xml:space="preserve"> 5 | <style type="text/css"> 6 | .st0{fill:#FFFFFF;} 7 | </style> 8 | <g id="Layer_2"> 9 | </g> 10 | <g id="Layer_1"> 11 | <g> 12 | <path class="st0" d="M79.9,31.8c2.6-6.3,2.6-19.1-0.8-28.9l0,0c-0.9-1.8-3.6-1.2-3.7,0.7v0.7c-0.6,9.4-4,14.5-9.1,17 13 | c-0.8,0.4-2.1,0.4-3-0.2c-6-3.8-13.2-6-20.9-6s-14.8,2.3-20.9,6c-0.8,0.5-1.8,0.6-2.6,0.2c-5.2-1.9-8.8-7.6-9.4-17.1V3.5 14 | C9.5,1.6,6.7,1,5.9,2.8c-3.4,10-3.3,22.8-0.8,29c1.3,3.1,1.3,6.6,0.2,9.8c-1.4,4-2.1,8.5-2,13c0.5,20.6,18,38.2,38.7,38.5 15 | C63.8,93.5,81.5,76,81.5,54.1c0-4.4-0.7-8.5-2-12.4C78.5,38.5,78.6,34.9,79.9,31.8z M41.9,83.9C26.1,83.5,13,70.6,12.8,54.7 16 | C12.4,37.8,26.2,24,43,24.4c15.9,0.2,28.9,13.3,29.2,29.2C72.5,70.4,58.8,84.1,41.9,83.9z"/> 17 | <path class="st0" d="M47,50.9l-7-10.8c-1.1-1.8-3.4-2.3-5.2-1.2c-1.1,0.7-1.8,1.9-1.8,3.2c0,0.7,0.2,1.4,0.6,2l4.7,7.4 18 | c0.4,0.6,0.2,1.3-0.1,1.8l-7.4,8.1c-1.4,1.5-1.3,3.9,0.2,5.3c0.7,0.6,1.7,0.9,2.6,0.9c1.1,0,2-0.5,2.7-1.2l5.5-6.4 19 | c0.5-0.5,1.2-0.5,1.5,0.1l3.9,5.6c0.2,0.5,0.6,0.8,1.1,1.1c1.3,0.9,3,0.9,4.3,0.1l0,0c1.1-0.7,1.8-1.9,1.8-3.2 20 | c0-0.7-0.2-1.4-0.6-2L47,50.9z"/> 21 | </g> 22 | </g> 23 | </svg> 24 | -------------------------------------------------------------------------------- /docs/src/components/theme/themeProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ThemeProvider as EmotionThemeProvider, Global, css } from '@emotion/react'; 3 | 4 | import { lightTheme, darkTheme } from './index'; 5 | import Header from '../Header'; 6 | import { baseStyles } from '../styles/GlobalStyles'; 7 | import { styles } from '../../custom/styles/styles'; 8 | 9 | class ThemeProvider extends React.Component { 10 | state = { 11 | isDarkThemeActive: false, 12 | }; 13 | 14 | componentDidMount() { 15 | this.retrieveActiveTheme(); 16 | } 17 | 18 | retrieveActiveTheme = () => { 19 | const isDarkThemeActive = JSON.parse(window.localStorage.getItem('isDarkThemeActive')); 20 | 21 | this.setState({ isDarkThemeActive }); 22 | }; 23 | 24 | toggleActiveTheme = () => { 25 | this.setState(prevState => ({ isDarkThemeActive: !prevState.isDarkThemeActive })); 26 | 27 | window.localStorage.setItem('isDarkThemeActive', JSON.stringify(!this.state.isDarkThemeActive)); 28 | }; 29 | 30 | render() { 31 | const { children, location } = this.props; 32 | 33 | const { isDarkThemeActive } = this.state; 34 | 35 | const currentActiveTheme = isDarkThemeActive ? darkTheme : lightTheme; 36 | 37 | return ( 38 | <div> 39 | <Global styles={[baseStyles, ...styles]} /> 40 | <Header 41 | location={location} 42 | isDarkThemeActive={isDarkThemeActive} 43 | toggleActiveTheme={this.toggleActiveTheme} 44 | /> 45 | <EmotionThemeProvider theme={currentActiveTheme}>{children}</EmotionThemeProvider> 46 | </div> 47 | ); 48 | } 49 | } 50 | 51 | export default ThemeProvider; 52 | -------------------------------------------------------------------------------- /docs/src/components/sidebar/treeNode.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import OpenedSvg from '../images/opened'; 3 | import ClosedSvg from '../images/closed'; 4 | import config from '../../../config'; 5 | import Link from '../link'; 6 | 7 | const TreeNode = ({ className = '', setCollapsed, collapsed, url, title, items, ...rest }) => { 8 | const isCollapsed = collapsed[url]; 9 | 10 | const collapse = () => { 11 | setCollapsed(url); 12 | }; 13 | 14 | const hasChildren = items.length !== 0; 15 | 16 | let location; 17 | 18 | if (typeof document != 'undefined') { 19 | location = document.location; 20 | } 21 | const active = 22 | location && (location.pathname === url || location.pathname === config.gatsby.pathPrefix + url); 23 | 24 | const calculatedClassName = `${className} item ${active ? 'active' : ''}`; 25 | 26 | return ( 27 | <li className={calculatedClassName}> 28 | {title && ( 29 | <Link to={url}> 30 | {title} 31 | {!config.sidebar.frontLine && title && hasChildren ? ( 32 | <button onClick={collapse} aria-label="collapse" className="collapser"> 33 | {!isCollapsed ? <OpenedSvg /> : <ClosedSvg />} 34 | </button> 35 | ) : null} 36 | </Link> 37 | )} 38 | 39 | {!isCollapsed && hasChildren ? ( 40 | <ul> 41 | {items.map((item, index) => ( 42 | <TreeNode 43 | key={item.url + index.toString()} 44 | setCollapsed={setCollapsed} 45 | collapsed={collapsed} 46 | {...item} 47 | /> 48 | ))} 49 | </ul> 50 | ) : null} 51 | </li> 52 | ); 53 | }; 54 | 55 | export default TreeNode; 56 | -------------------------------------------------------------------------------- /src/core/matcher/state.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | use crate::core::{ 4 | matcher::{CaptureItem, MatchedItem}, 5 | node::ConsecutiveNodes, 6 | query::MetavariableId, 7 | }; 8 | use std::collections::HashMap; 9 | 10 | pub type UnverifiedMetavariable<'tree> = (MetavariableId, CaptureItem<'tree>); 11 | 12 | #[derive(Debug, Default, Clone)] 13 | pub struct MatcherState<'tree> { 14 | pub(crate) subtree: Option<ConsecutiveNodes<'tree>>, 15 | pub(crate) captures: Vec<UnverifiedMetavariable<'tree>>, 16 | } 17 | 18 | impl<'tree> From<MatcherState<'tree>> for Option<MatchedItem<'tree>> { 19 | fn from(value: MatcherState<'tree>) -> Self { 20 | let mut captures = HashMap::<MetavariableId, CaptureItem>::new(); 21 | 22 | let captures_per_mid = value.captures.into_iter().group_by(|k| k.0.clone()); 23 | for (mid, citems) in captures_per_mid.into_iter() { 24 | if mid == MetavariableId("_".into()) { 25 | continue; 26 | } 27 | let capture_items = citems.into_iter().map(|x| x.1).collect(); 28 | if let Some(c) = fold_capture(capture_items) { 29 | captures.insert(mid, c); 30 | } else { 31 | return None; 32 | } 33 | } 34 | 35 | Some(MatchedItem { 36 | area: value.subtree.unwrap(), 37 | captures, 38 | }) 39 | } 40 | } 41 | 42 | fn fold_capture(capture_items: Vec<CaptureItem<'_>>) -> Option<CaptureItem<'_>> { 43 | let mut it = capture_items.into_iter(); 44 | let first = it.next(); 45 | it.fold(first, |acc, capture| match acc { 46 | Some(acc) => { 47 | if acc.as_str() == capture.as_str() { 48 | Some(capture) 49 | } else { 50 | None 51 | } 52 | } 53 | None => None, 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! ruleset_test { 3 | ($($name:ident: $value:expr,)*) => { 4 | $( 5 | #[test] 6 | fn $name() { 7 | use crate::cli::opts; 8 | use crate::cli::reporter::ReporterType; 9 | use crate::cli::subcommand; 10 | use anyhow::Result; 11 | use clap_verbosity_flag::Verbosity; 12 | use std::path::PathBuf; 13 | 14 | for (lvalue, rvalue, mitem_num, encoding) in $value { 15 | let mut ruleset = PathBuf::from(file!()); 16 | ruleset.pop(); 17 | ruleset.push(stringify!($name)); 18 | ruleset.push(lvalue); 19 | 20 | let mut target = PathBuf::from(file!()); 21 | target.pop(); 22 | target.push(stringify!($name)); 23 | target.push(rvalue); 24 | 25 | let mitem_num: Result<usize> = mitem_num; 26 | let r = subcommand::check::handle_opts(subcommand::check::CheckOpts{ 27 | common: opts::CommonOpts { verbose: Verbosity::new(0, 0, 0) }, 28 | report: opts::ReportOpts { format: ReporterType::Console, }, 29 | ruleset_path: ruleset, 30 | encoding: encoding, 31 | target_path: Some(target), 32 | exit_zero: false, 33 | exclude: vec![], 34 | }); 35 | match (r, mitem_num) { 36 | (Ok(x), Ok(y)) if x == y => (), 37 | (Err(_), Err(_)) => (), 38 | (x, y) => panic!("[{}] {} + {}: expected {:?}, got {:?}", stringify!($name), lvalue, rvalue, y, x), 39 | } 40 | } 41 | } 42 | )* 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod generic; 48 | 49 | #[cfg(test)] 50 | mod hcl; 51 | -------------------------------------------------------------------------------- /docs/src/components/styles/Docs.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const StyledHeading = styled('h1')` 4 | font-size: 32px; 5 | line-height: 1.5; 6 | font-weight: 500; 7 | border-left: 2px solid #1ed3c6; 8 | padding: 0 16px; 9 | flex: 1; 10 | margin-top: 0; 11 | padding-top: 0; 12 | color: ${props => props.theme.colors.heading}; 13 | `; 14 | 15 | export const Edit = styled('div')` 16 | padding: 1rem 1.5rem; 17 | text-align: right; 18 | 19 | a { 20 | font-size: 14px; 21 | font-weight: 500; 22 | line-height: 1em; 23 | text-decoration: none; 24 | color: #555; 25 | border: 1px solid rgb(211, 220, 228); 26 | cursor: pointer; 27 | border-radius: 3px; 28 | transition: all 0.2s ease-out 0s; 29 | text-decoration: none; 30 | color: rgb(36, 42, 49); 31 | background-color: rgb(255, 255, 255); 32 | box-shadow: rgba(116, 129, 141, 0.1) 0px 1px 1px 0px; 33 | height: 30px; 34 | padding: 5px 16px; 35 | &:hover { 36 | background-color: rgb(245, 247, 249); 37 | } 38 | } 39 | `; 40 | 41 | export const StyledMainWrapper = styled.div` 42 | max-width: 750px; 43 | color: ${props => props.theme.colors.text}; 44 | 45 | ul, 46 | ol { 47 | -webkit-padding-start: 40px; 48 | -moz-padding-start: 40px; 49 | -o-padding-start: 40px; 50 | margin: 24px 0px; 51 | padding: 0px 0px 0px 2em; 52 | 53 | li { 54 | font-size: 16px; 55 | line-height: 1.8; 56 | font-weight: 400; 57 | } 58 | } 59 | 60 | a { 61 | transition: color 0.15s; 62 | color: ${props => props.theme.colors.link}; 63 | } 64 | 65 | code { 66 | border: 1px solid #ede7f3; 67 | border-radius: 4px; 68 | padding: 2px 6px; 69 | font-size: 0.9375em; 70 | 71 | background: ${props => props.theme.colors.background}; 72 | } 73 | 74 | @media (max-width: 767px) { 75 | padding: 0 15px; 76 | } 77 | `; 78 | -------------------------------------------------------------------------------- /docs/src/components/mdxComponents/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import CodeBlock from './codeBlock'; 5 | import AnchorTag from './anchor'; 6 | 7 | const StyledPre = styled('pre')` 8 | padding: 16px; 9 | background: ${props => props.theme.colors.preFormattedText}; 10 | `; 11 | 12 | const appendString = (children) => { 13 | if(Array.isArray(children)) { 14 | return children.reduce((acc, current) => { 15 | if(typeof current === "string") { 16 | return acc.concat(current) 17 | } else if (typeof current === "object") { 18 | return acc.concat(current.props.children) 19 | } else { 20 | return acc 21 | } 22 | }, "") 23 | } else { 24 | return children 25 | } 26 | } 27 | 28 | export default { 29 | h1: props => ( 30 | <h1 className="heading1" id={appendString(props.children).replace(/\s+/g, '').toLowerCase()} {...props} /> 31 | ), 32 | h2: props => ( 33 | <h2 className="heading2" id={appendString(props.children).replace(/\s+/g, '').toLowerCase()} {...props} /> 34 | ), 35 | h3: props => ( 36 | <h3 className="heading3" id={appendString(props.children).replace(/\s+/g, '').toLowerCase()} {...props} /> 37 | ), 38 | h4: props => ( 39 | <h4 className="heading4" id={appendString(props.children).replace(/\s+/g, '').toLowerCase()} {...props} /> 40 | ), 41 | h5: props => ( 42 | <h5 className="heading5" id={appendString(props.children).replace(/\s+/g, '').toLowerCase()} {...props} /> 43 | ), 44 | h6: props => ( 45 | <h6 className="heading6" id={appendString(props.children).replace(/\s+/g, '').toLowerCase()} {...props} /> 46 | ), 47 | p: props => <p className="paragraph" {...props} />, 48 | pre: props => ( 49 | <StyledPre> 50 | <pre {...props} /> 51 | </StyledPre> 52 | ), 53 | code: CodeBlock, 54 | a: AnchorTag, 55 | // TODO add `img` 56 | // TODO add `blockquote` 57 | // TODO add `ul` 58 | // TODO add `li` 59 | // TODO add `table` 60 | }; 61 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "docs" 7 | - ".vscode" 8 | 9 | jobs: 10 | test: 11 | timeout-minutes: 10 12 | strategy: 13 | matrix: 14 | rust: 15 | - stable 16 | os: [ubuntu-latest] 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout HEAD 20 | uses: actions/checkout@v1 21 | with: 22 | submodules: true 23 | - name: Setup Rust 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: ${{ matrix.rust }} 28 | override: true 29 | - name: Build 30 | run: cargo build 31 | - name: Run tests 32 | run: cargo test 33 | 34 | clippy: 35 | strategy: 36 | matrix: 37 | rust: 38 | - stable 39 | os: [ubuntu-latest] 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout HEAD 43 | uses: actions/checkout@v1 44 | with: 45 | submodules: true 46 | - name: Setup Rust 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: ${{ matrix.rust }} 51 | override: true 52 | - run: rustup component add clippy 53 | - uses: actions-rs/clippy-check@v1 54 | with: 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | args: --all-features 57 | 58 | shisho: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Checkout HEAD 62 | uses: actions/checkout@v1 63 | - name: Run Shisho 64 | uses: flatt-security/shisho-action@main 65 | with: 66 | ruleset-path: "./rules" 67 | target-path: "./" 68 | output-format: "sarif" 69 | output-path: "shisho.sarif" 70 | succeed-always: true 71 | paths-ignore: "./third_party,./docs" 72 | - name: Upload SARIF file 73 | uses: github/codeql-action/upload-sarif@v1 74 | with: 75 | sarif_file: ./shisho.sarif 76 | -------------------------------------------------------------------------------- /docs/src/html.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import config from '../config'; 4 | 5 | export default class HTML extends React.Component { 6 | render() { 7 | return ( 8 | <html {...this.props.htmlAttributes} lang="en"> 9 | <head> 10 | <meta charSet="utf-8" /> 11 | <meta httpEquiv="x-ua-compatible" content="ie=edge" /> 12 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 13 | {config.siteMetadata.ogImage ? ( 14 | <meta property="og:image" content={config.siteMetadata.ogImage} /> 15 | ) : null} 16 | <meta property="twitter:card" content="summary_large_image" /> 17 | {config.siteMetadata.ogImage ? ( 18 | <meta property="twitter:image" content={config.siteMetadata.ogImage} /> 19 | ) : null} 20 | {config.siteMetadata.favicon ? ( 21 | <link rel="shortcut icon" type="image/svg" href={config.siteMetadata.favicon} /> 22 | ) : null} 23 | <noscript key="noscript"></noscript> 24 | {this.props.headComponents} 25 | </head> 26 | <body {...this.props.bodyAttributes}> 27 | {this.props.preBodyComponents} 28 | <div key={`body`} id="___gatsby" dangerouslySetInnerHTML={{ __html: this.props.body }} /> 29 | {this.props.postBodyComponents} 30 | <script 31 | defer 32 | dangerouslySetInnerHTML={{ 33 | __html: ` 34 | function navBarClose() { 35 | document.getElementById("navbar").classList.toggle("responsive"); 36 | } 37 | document.addEventListener('click',function(e){ 38 | if(e.target && e.target.tagName.toLowerCase() === 'a'){ 39 | navBarClose(); 40 | } 41 | }); 42 | `, 43 | }} 44 | /> 45 | </body> 46 | </html> 47 | ); 48 | } 49 | } 50 | 51 | HTML.propTypes = { 52 | htmlAttributes: PropTypes.object, 53 | headComponents: PropTypes.array, 54 | bodyAttributes: PropTypes.object, 55 | preBodyComponents: PropTypes.array, 56 | body: PropTypes.string, 57 | postBodyComponents: PropTypes.array, 58 | }; 59 | -------------------------------------------------------------------------------- /docs/src/components/rightSidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StaticQuery, graphql } from 'gatsby'; 3 | 4 | // import Link from './link'; 5 | import config from '../../config'; 6 | import { Sidebar, ListItem } from './styles/Sidebar'; 7 | 8 | const SidebarLayout = ({ location }) => ( 9 | <StaticQuery 10 | query={graphql` 11 | query { 12 | allMdx { 13 | edges { 14 | node { 15 | fields { 16 | slug 17 | } 18 | tableOfContents 19 | } 20 | } 21 | } 22 | } 23 | `} 24 | render={({ allMdx }) => { 25 | let navItems = []; 26 | 27 | let finalNavItems; 28 | 29 | if (allMdx.edges !== undefined && allMdx.edges.length > 0) { 30 | const navItems = allMdx.edges.map((item, index) => { 31 | let innerItems; 32 | 33 | if (item !== undefined) { 34 | if ( 35 | item.node.fields.slug === location.pathname || 36 | config.gatsby.pathPrefix + item.node.fields.slug === location.pathname 37 | ) { 38 | if (item.node.tableOfContents.items) { 39 | innerItems = item.node.tableOfContents.items.map((innerItem, index) => { 40 | const itemId = innerItem.title 41 | ? innerItem.title.replace(/\s+/g, '').toLowerCase() 42 | : '#'; 43 | 44 | return ( 45 | <ListItem key={index} to={`#${itemId}`} level={1}> 46 | {innerItem.title} 47 | </ListItem> 48 | ); 49 | }); 50 | } 51 | } 52 | } 53 | if (innerItems) { 54 | finalNavItems = innerItems; 55 | } 56 | }); 57 | } 58 | 59 | if (finalNavItems && finalNavItems.length) { 60 | return ( 61 | <Sidebar> 62 | <ul className={'rightSideBarUL'}> 63 | <li className={'rightSideTitle'}>CONTENTS</li> 64 | {finalNavItems} 65 | </ul> 66 | </Sidebar> 67 | ); 68 | } else { 69 | return ( 70 | <Sidebar> 71 | <ul></ul> 72 | </Sidebar> 73 | ); 74 | } 75 | }} 76 | /> 77 | ); 78 | 79 | export default SidebarLayout; 80 | -------------------------------------------------------------------------------- /docs/src/components/styles/Sidebar.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Sidebar = styled('aside')` 4 | width: 100%; 5 | border-right: 1px solid #ede7f3; 6 | height: 100vh; 7 | overflow: auto; 8 | position: fixed; 9 | padding-left: 24px; 10 | position: -webkit-sticky; 11 | position: -moz-sticky; 12 | position: sticky; 13 | top: 0; 14 | 15 | background: ${props => props.theme.colors.background}; 16 | 17 | .rightSideTitle { 18 | font-size: 10px; 19 | line-height: 1; 20 | font-weight: 700; 21 | text-transform: uppercase; 22 | letter-spacing: 1.2px; 23 | padding: 7px 24px 7px 16px; 24 | border-left: 1px solid #e6ecf1; 25 | border-left-color: rgb(230, 236, 241); 26 | 27 | color: ${props => props.theme.colors.text}; 28 | } 29 | 30 | .rightSideBarUL { 31 | margin-top: 32px; 32 | } 33 | 34 | .rightSideBarUL li { 35 | list-style-type: none; 36 | border-left: 1px solid #e6ecf1; 37 | border-left-color: rgb(230, 236, 241); 38 | } 39 | 40 | .rightSideBarUL li a { 41 | font-size: 12px; 42 | font-weight: 500; 43 | line-height: 1.5; 44 | padding: 7px 24px 7px 16px; 45 | 46 | color: ${props => props.theme.colors.text}; 47 | } 48 | 49 | @media only screen and (max-width: 50rem) { 50 | width: 100%; 51 | position: relative; 52 | } 53 | `; 54 | 55 | export const ListItem = styled(({ className, active, level, ...props }) => { 56 | return ( 57 | <li className={className}> 58 | <a href={props.to} {...props}> 59 | {props.children} 60 | </a> 61 | </li> 62 | ); 63 | })` 64 | list-style: none; 65 | 66 | a { 67 | color: #5c6975; 68 | text-decoration: none; 69 | font-weight: ${({ level }) => (level === 0 ? 700 : 400)}; 70 | padding: 0.45rem 0 0.45rem ${props => 2 + (props.level || 0) * 1}rem; 71 | display: block; 72 | position: relative; 73 | 74 | &:hover { 75 | color: #1ed3c6 !important; 76 | } 77 | 78 | ${props => 79 | props.active && 80 | ` 81 | color: #1ED3C6; 82 | border-color: rgb(230,236,241) !important; 83 | border-style: solid none solid solid; 84 | border-width: 1px 0px 1px 1px; 85 | background-color: #fff; 86 | `} // external link icon 87 | svg { 88 | float: right; 89 | margin-right: 1rem; 90 | } 91 | } 92 | `; 93 | -------------------------------------------------------------------------------- /docs/src/components/DarkModeSwitch.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import NightImage from './images/night.png'; 5 | import DayImage from './images/day.png'; 6 | 7 | const StyledSwitch = styled('div')` 8 | display: flex; 9 | justify-content: flex-end; 10 | width: 100%; 11 | padding: 0 20px 0 25px; 12 | 13 | /* The switch - the box around the slider */ 14 | .switch { 15 | position: relative; 16 | display: inline-block; 17 | width: 50px; 18 | height: 20px; 19 | } 20 | 21 | /* Hide default HTML checkbox */ 22 | .switch input { 23 | opacity: 0; 24 | width: 0; 25 | height: 0; 26 | } 27 | 28 | /* The slider */ 29 | .slider { 30 | position: absolute; 31 | cursor: pointer; 32 | top: 0; 33 | left: 0; 34 | right: 0; 35 | bottom: 0; 36 | background-color: #ccc; 37 | -webkit-transition: 0.4s; 38 | transition: 0.4s; 39 | } 40 | 41 | .slider:before { 42 | position: absolute; 43 | content: ''; 44 | height: 30px; 45 | width: 30px; 46 | left: 0px; 47 | bottom: 4px; 48 | top: 0; 49 | bottom: 0; 50 | margin: auto 0; 51 | -webkit-transition: 0.4s; 52 | transition: 0.4s; 53 | box-shadow: 0 0px 15px #2020203d; 54 | background: white url(${NightImage}); 55 | background-repeat: no-repeat; 56 | background-position: center; 57 | } 58 | 59 | input:checked + .slider { 60 | background: linear-gradient(to right, #fefb72, #f0bb31); 61 | } 62 | 63 | input:checked + .slider:before { 64 | -webkit-transform: translateX(24px); 65 | -ms-transform: translateX(24px); 66 | transform: translateX(24px); 67 | background: white url(${DayImage}); 68 | background-repeat: no-repeat; 69 | background-position: center; 70 | } 71 | 72 | /* Rounded sliders */ 73 | .slider.round { 74 | border-radius: 34px; 75 | } 76 | 77 | .slider.round:before { 78 | border-radius: 50%; 79 | } 80 | `; 81 | 82 | export const DarkModeSwitch = ({ isDarkThemeActive, toggleActiveTheme }) => ( 83 | <StyledSwitch> 84 | <label id="switch" className="switch"> 85 | <input 86 | type="checkbox" 87 | id="slider" 88 | onChange={toggleActiveTheme} 89 | checked={isDarkThemeActive ? false : true} 90 | /> 91 | <span className="slider round"></span> 92 | </label> 93 | </StyledSwitch> 94 | ); 95 | -------------------------------------------------------------------------------- /docs/src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { MDXProvider } from '@mdx-js/react'; 4 | 5 | import ThemeProvider from './theme/themeProvider'; 6 | import mdxComponents from './mdxComponents'; 7 | import Sidebar from './sidebar'; 8 | import RightSidebar from './rightSidebar'; 9 | import config from '../../config.js'; 10 | 11 | const Wrapper = styled('div')` 12 | display: flex; 13 | justify-content: space-between; 14 | background: ${({ theme }) => theme.colors.background}; 15 | 16 | .sideBarUL li a { 17 | color: ${({ theme }) => theme.colors.text}; 18 | } 19 | 20 | .sideBarUL .item > a:hover { 21 | background-color: #1ed3c6; 22 | color: #fff !important; 23 | 24 | /* background: #F8F8F8 */ 25 | } 26 | 27 | @media only screen and (max-width: 767px) { 28 | display: block; 29 | } 30 | `; 31 | 32 | const Content = styled('main')` 33 | display: flex; 34 | flex-grow: 1; 35 | margin: 0px 88px; 36 | padding-top: 3rem; 37 | background: ${({ theme }) => theme.colors.background}; 38 | 39 | table tr { 40 | background: ${({ theme }) => theme.colors.background}; 41 | } 42 | 43 | @media only screen and (max-width: 1023px) { 44 | padding-left: 0; 45 | margin: 0 10px; 46 | padding-top: 3rem; 47 | } 48 | `; 49 | 50 | const MaxWidth = styled('div')` 51 | @media only screen and (max-width: 50rem) { 52 | width: 100%; 53 | position: relative; 54 | } 55 | `; 56 | 57 | const LeftSideBarWidth = styled('div')` 58 | width: 298px; 59 | `; 60 | 61 | const RightSideBarWidth = styled('div')` 62 | width: 224px; 63 | `; 64 | 65 | const Layout = ({ children, location }) => ( 66 | <ThemeProvider location={location}> 67 | <MDXProvider components={mdxComponents}> 68 | <Wrapper> 69 | <LeftSideBarWidth className={'hiddenMobile'}> 70 | <Sidebar location={location} /> 71 | </LeftSideBarWidth> 72 | {config.sidebar.title ? ( 73 | <div 74 | className={'sidebarTitle sideBarShow'} 75 | dangerouslySetInnerHTML={{ __html: config.sidebar.title }} 76 | /> 77 | ) : null} 78 | <Content> 79 | <MaxWidth>{children}</MaxWidth> 80 | </Content> 81 | <RightSideBarWidth className={'hiddenMobile'}> 82 | <RightSidebar location={location} /> 83 | </RightSideBarWidth> 84 | </Wrapper> 85 | </MDXProvider> 86 | </ThemeProvider> 87 | ); 88 | 89 | export default Layout; 90 | -------------------------------------------------------------------------------- /docs/src/components/search/input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connectSearchBox } from 'react-instantsearch-dom'; 3 | 4 | import styled from '@emotion/styled'; 5 | import { css } from '@emotion/react'; 6 | import { Search } from '@styled-icons/fa-solid/Search'; 7 | 8 | const SearchIcon = styled(Search)` 9 | width: 1em; 10 | pointer-events: none; 11 | margin-right: 10px; 12 | position: absolute; 13 | left: 15px; 14 | color: #2fd2c5; 15 | `; 16 | 17 | const focus = props => css` 18 | background: white; 19 | color: ${props => props.theme.darkBlue}; 20 | cursor: text; 21 | width: 5em; 22 | + ${SearchIcon} { 23 | color: ${props => props.theme.darkBlue}; 24 | margin: 0.3em; 25 | } 26 | `; 27 | 28 | const collapse = props => css` 29 | width: 0; 30 | cursor: pointer; 31 | color: ${props => props.theme.lightBlue}; 32 | + ${SearchIcon} { 33 | color: white; 34 | } 35 | ${props => props.focus && focus()} 36 | margin-left: ${props => (props.focus ? `-1.6em` : `-1em`)}; 37 | padding-left: ${props => (props.focus ? `1.6em` : `1em`)}; 38 | ::placeholder { 39 | color: ${props => props.theme.gray}; 40 | } 41 | `; 42 | 43 | const expand = props => css` 44 | background: ${props => props.theme.veryLightGray}; 45 | width: 6em; 46 | margin-left: -1.6em; 47 | padding-left: 1.6em; 48 | + ${SearchIcon} { 49 | margin: 0.3em; 50 | } 51 | `; 52 | 53 | const collapseExpand = props => css` 54 | ${props => (props.collapse ? collapse() : expand())} 55 | `; 56 | 57 | const Input = styled.input` 58 | outline: none; 59 | border: none; 60 | font-size: 1em; 61 | background: white; 62 | transition: ${props => props.theme.shortTrans}; 63 | border-radius: ${props => props.theme.smallBorderRadius}; 64 | {collapseExpand} 65 | `; 66 | 67 | const Form = styled.form` 68 | display: flex; 69 | align-items: center; 70 | @media only screen and (max-width: 767px) { 71 | width: 100%; 72 | margin-left: 15px; 73 | } 74 | `; 75 | 76 | export default connectSearchBox(({ refine, ...rest }) => { 77 | const preventSubmit = e => { 78 | e.preventDefault(); 79 | }; 80 | 81 | return ( 82 | <Form className={'formElement'} onSubmit={preventSubmit}> 83 | <SearchIcon /> 84 | <Input 85 | className={'searchInput '} 86 | type="text" 87 | placeholder="Search" 88 | aria-label="Search" 89 | onChange={e => refine(e.target.value)} 90 | {...rest} 91 | /> 92 | </Form> 93 | ); 94 | }); 95 | -------------------------------------------------------------------------------- /src/cli/reporter/json.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use super::Reporter; 4 | use crate::core::{ 5 | language::Queryable, matcher::MatchedItem, node::Range, pattern::Pattern, ruleset::Rule, 6 | source::Code, target::Target, 7 | }; 8 | use anyhow::Result; 9 | use serde::{Deserialize, Serialize}; 10 | use similar::TextDiff; 11 | 12 | pub struct JSONReporter<'a, Writer: std::io::Write> { 13 | writer: &'a mut Writer, 14 | entries: Vec<Entry>, 15 | } 16 | 17 | #[derive(Debug, Serialize, Deserialize)] 18 | struct Entry { 19 | pub id: String, 20 | pub location: Location, 21 | pub rewrite: Vec<JSONPatch>, 22 | } 23 | 24 | #[derive(Debug, Serialize, Deserialize)] 25 | struct Location { 26 | file: String, 27 | range: Range, 28 | } 29 | 30 | #[derive(Debug, Serialize, Deserialize)] 31 | struct JSONPatch { 32 | pub diff: String, 33 | } 34 | 35 | impl<'a, W: std::io::Write> Reporter<'a> for JSONReporter<'a, W> { 36 | type Writer = W; 37 | fn new(writer: &'a mut Self::Writer) -> Self { 38 | Self { 39 | writer, 40 | entries: vec![], 41 | } 42 | } 43 | 44 | fn add_entry<T: Queryable>( 45 | &mut self, 46 | target: &Target, 47 | items: Vec<(&Rule, MatchedItem)>, 48 | ) -> Result<()> { 49 | for (rule, mitem) in items { 50 | let mut r = Entry { 51 | id: rule.id.clone(), 52 | location: Location { 53 | file: target.relative_path(), 54 | range: mitem.area.range::<T>(), 55 | }, 56 | rewrite: vec![], 57 | }; 58 | 59 | for rewrite in rule.get_rewrite_options()? { 60 | let old_code: Code<T> = target.body.clone().into(); 61 | let pattern = Pattern::try_from(rewrite.as_str())?; 62 | let new_code = old_code.to_rewritten_form(&mitem, pattern.as_rewrite_option())?; 63 | 64 | let diff = TextDiff::from_lines(target.body.as_str(), new_code.as_str()) 65 | .unified_diff() 66 | .to_string(); 67 | r.rewrite.push(JSONPatch { diff }); 68 | } 69 | 70 | self.entries.push(r); 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | fn report(&mut self) -> Result<()> { 77 | let s = serde_json::to_string(&self.entries)?; 78 | self.entries = vec![]; 79 | write!(self.writer, "{}", s)?; 80 | Ok(()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /docs/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | gatsby: { 3 | pathPrefix: '/', 4 | siteUrl: 'https://docs.shisho.dev', 5 | gaTrackingId: 'UA-145321226-11', 6 | trailingSlash: false, 7 | }, 8 | header: { 9 | logo: '/images/logo-white.png', 10 | logoLink: '/', 11 | title: '', 12 | githubUrl: 'https://github.com/flatt-security/shisho', 13 | helpUrl: '', 14 | tweetText: '', 15 | social: `<li> 16 | <a href="https://twitter.com/flatt_sec_en" target="_blank" rel="noopener"> 17 | <div class="twitterBtn"> 18 | <img src='/images/twitter-brands-block.svg' alt={'Twitter'}/> 19 | </div> 20 | </a> 21 | </li>`, 22 | links: [{ text: '', link: '' }], 23 | search: { 24 | enabled: false, 25 | indexName: '', 26 | algoliaAppId: process.env.GATSBY_ALGOLIA_APP_ID, 27 | algoliaSearchKey: process.env.GATSBY_ALGOLIA_SEARCH_KEY, 28 | algoliaAdminKey: process.env.ALGOLIA_ADMIN_KEY, 29 | }, 30 | }, 31 | sidebar: { 32 | forcedNavOrder: ['/', '/shisho', '/shisho-cloud', '/shisho-action', '/shisho-dojo', '/roadmap'], 33 | collapsedNav: [], 34 | links: [ 35 | { text: 'Shisho Cloud', link: 'https://shisho.dev' }, 36 | { text: 'Shisho Playground', link: 'https://play.shisho.dev/' }, 37 | { text: 'Shisho Dojo', link: 'https://shisho.dev/dojo/' }, 38 | { text: 'Flatt Security Inc.', link: 'https://flatt.tech/en' }, 39 | ], 40 | frontline: false, 41 | ignoreIndex: false, 42 | title: '', 43 | }, 44 | siteMetadata: { 45 | title: 'Shisho Documentation', 46 | description: 'Shisho Documentation explains the details of usage and benefits and answers any questions regarding Shisho products.', 47 | ogImage: 'https://docs.shisho.dev/images/ogp.png', 48 | docsLocation: 'https://github.com/flatt-security/shisho/tree/main/docs/content', 49 | favicon: 'https://docs.shisho.dev/favicon.png', 50 | }, 51 | pwa: { 52 | enabled: false, // disabling this will also remove the existing service worker. 53 | manifest: { 54 | name: 'Shisho', 55 | short_name: 'Shisho', 56 | start_url: '/', 57 | background_color: '#032273', 58 | theme_color: '#032273', 59 | display: 'standalone', 60 | crossOrigin: 'use-credentials', 61 | icons: [ 62 | { 63 | src: 'src/pwa-512.png', 64 | sizes: `512x512`, 65 | type: `image/png`, 66 | }, 67 | ], 68 | }, 69 | }, 70 | }; 71 | 72 | module.exports = config; 73 | -------------------------------------------------------------------------------- /src/core/language.rs: -------------------------------------------------------------------------------- 1 | mod docker; 2 | mod go; 3 | mod hcl; 4 | 5 | use crate::core::node::Position; 6 | 7 | pub use self::docker::Dockerfile; 8 | pub use self::go::Go; 9 | pub use self::hcl::HCL; 10 | 11 | use super::node::{Node, Range, RootNode}; 12 | 13 | pub trait Queryable { 14 | fn target_language() -> tree_sitter::Language; 15 | fn query_language() -> tree_sitter::Language; 16 | 17 | /// `unwrap_root` takes a root of the query tree and returns nodes for matching. 18 | fn unwrap_root<'tree, 'a>(root: &'a RootNode<'tree>) -> &'a Vec<Node<'tree>>; 19 | 20 | /// `is_skippable` returns whether the given node could be ignored on matching. 21 | fn is_skippable(_node: &Node) -> bool { 22 | false 23 | } 24 | 25 | fn is_leaf_like(_node: &Node) -> bool { 26 | false 27 | } 28 | 29 | fn is_string_literal(_node: &Node) -> bool { 30 | false 31 | } 32 | 33 | fn range(node: &Node) -> Range { 34 | Self::default_range(node) 35 | } 36 | 37 | fn default_range(node: &Node) -> Range { 38 | if node.as_str().ends_with('\n') { 39 | Range { 40 | start: Position { 41 | row: node.start_position().row + 1, 42 | column: node.start_position().column + 1, 43 | }, 44 | end: Position { 45 | row: node.end_position().row + 1, 46 | column: 0, 47 | }, 48 | } 49 | } else { 50 | Range { 51 | start: Position { 52 | row: node.start_position().row + 1, 53 | column: node.start_position().column + 1, 54 | }, 55 | end: Position { 56 | row: node.end_position().row + 1, 57 | column: node.end_position().column + 1, 58 | }, 59 | } 60 | } 61 | } 62 | 63 | fn node_value_eq<'a, 'b>(l: &Node<'a>, r: &Node<'b>) -> bool { 64 | *l.as_str() == *r.as_str() 65 | } 66 | } 67 | 68 | #[macro_export] 69 | macro_rules! match_pt { 70 | ($lang:ident, $p:tt, $t:tt, $callback:expr) => {{ 71 | let pattern = crate::core::pattern::Pattern::<$lang>::try_from($p).unwrap(); 72 | let pc = crate::core::pattern::PatternWithConstraints::new(pattern, vec![]); 73 | 74 | let query = pc.as_query(); 75 | let tree = crate::core::tree::Tree::<$lang>::try_from($t).unwrap(); 76 | let ptree = crate::core::tree::NormalizedTree::from(&tree); 77 | let ptree = ptree.as_ref_treeview(); 78 | let session = ptree.matches(&query); 79 | 80 | $callback(session.collect::<anyhow::Result<Vec<crate::core::matcher::MatchedItem>>>()); 81 | }}; 82 | } 83 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shisho-docs", 3 | "private": true, 4 | "description": "Documentation for Shisho", 5 | "author": "Flatt Security Inc.<t.yoneuchi@flatt.tech>", 6 | "version": "0.0.1", 7 | "dependencies": { 8 | "@babel/plugin-proposal-export-default-from": "^7.12.13", 9 | "@emotion/react": "^11.1.5", 10 | "@emotion/styled": "^11.3.0", 11 | "@emotion/styled-base": "^11.0.0", 12 | "@mdx-js/loader": "^1.6.22", 13 | "@mdx-js/mdx": "^1.6.22", 14 | "@mdx-js/react": "^1.6.22", 15 | "@philpl/buble": "^0.19.7", 16 | "@playlyfe/gql": "^2.6.2", 17 | "@styled-icons/fa-brands": "^10.26.0", 18 | "@styled-icons/fa-solid": "^10.32.0", 19 | "algoliasearch": "^4.9.1", 20 | "dotenv": "^8.5.1", 21 | "emotion": "^11.0.0", 22 | "emotion-server": "^11.0.0", 23 | "gatsby": "^3.4.1", 24 | "gatsby-link": "^3.4.0", 25 | "gatsby-plugin-algolia": "^0.19.0", 26 | "gatsby-plugin-emotion": "^6.4.0", 27 | "gatsby-plugin-gtag": "^1.0.13", 28 | "gatsby-plugin-layout": "^2.4.0", 29 | "gatsby-plugin-manifest": "^3.4.0", 30 | "gatsby-plugin-mdx": "^2.14.1", 31 | "gatsby-plugin-offline": "^4.4.0", 32 | "gatsby-plugin-react-helmet": "^4.4.0", 33 | "gatsby-plugin-remove-serviceworker": "^1.0.0", 34 | "gatsby-plugin-sharp": "^3.4.1", 35 | "gatsby-plugin-sitemap": "^4.0.0", 36 | "gatsby-remark-copy-linked-files": "^4.1.0", 37 | "gatsby-remark-images": "^5.1.0", 38 | "gatsby-source-filesystem": "^3.4.0", 39 | "gatsby-transformer-remark": "^4.1.0", 40 | "graphql": "^15.5.0", 41 | "is-absolute-url": "^3.0.3", 42 | "lodash.flatten": "^4.4.0", 43 | "lodash.startcase": "^4.4.0", 44 | "prismjs": "^1.27.0", 45 | "react": "^17.0.2", 46 | "react-dom": "^17.0.2", 47 | "react-feather": "^2.0.9", 48 | "react-github-btn": "^1.2.0", 49 | "react-helmet": "^6.1.0", 50 | "react-id-generator": "^3.0.1", 51 | "react-instantsearch-dom": "^6.11.0", 52 | "react-live": "^2.2.3", 53 | "react-loadable": "^5.5.0", 54 | "styled-components": "^5.3.0" 55 | }, 56 | "license": "MIT", 57 | "main": "n/a", 58 | "scripts": { 59 | "start": "gatsby develop", 60 | "build": "gatsby build --prefix-paths", 61 | "format": "prettier --write \"src/**/*.{js,jsx,css,json}\"", 62 | "lint": "eslint --fix \"src/**/*.{js,jsx}\"" 63 | }, 64 | "devDependencies": { 65 | "babel-eslint": "^10.1.0", 66 | "eslint": "^7.25.0", 67 | "eslint-config-prettier": "^8.3.0", 68 | "eslint-plugin-import": "^2.22.1", 69 | "eslint-plugin-jsx-a11y": "^6.4.1", 70 | "eslint-plugin-prettier": "^3.4.0", 71 | "eslint-plugin-react": "^7.23.2", 72 | "gatsby-plugin-remove-trailing-slashes": "^3.4.0", 73 | "prettier": "^2.2.1", 74 | "prism-react-renderer": "^1.2.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /docs/content/shisho.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Shisho' 3 | metaTitle: 'Shisho Engine Documentation' 4 | metaDescription: 'This page describes details of Shisho.' 5 | --- 6 | 7 | ## Introduction 8 | 9 | **Shisho** is a **lightweight static code analyzer** designed for developers and is the **core engine** for Shisho products. It is, so to speak, like a pluggable and configurable linter; it gives developers a way to codify your domain knowledge over your code as *rules*. With powerful automation and integration capabilities, the rules will help you find and fix issues semiautomatically. 10 | 11 | ![demo](./images/shisho-demo.gif) 12 | 13 | ## Key Concept: Detection-as-Code for Code 14 | 15 | Shisho provides a means of **achieving Detection-as-Code for your code**. It allows us to analyze and transform your source code with our intuitive DSL. Here's an example of policies for Terraform code: 16 | 17 | ```yaml 18 | version: '1' 19 | rules: 20 | - id: 'unencrypted-ebs-volume' 21 | language: hcl 22 | message: | 23 | There was unencrypted EBS module. 24 | pattern: | 25 | resource "aws_ebs_volume" :[NAME] { 26 | :[...X] 27 | } 28 | constraints: 29 | - target: X 30 | should: not-match 31 | pattern: | 32 | encrypted = true 33 | rewrite: | 34 | resource "aws_ebs_volume" :[NAME] { 35 | :[X] 36 | encrypted = true 37 | } 38 | ``` 39 | 40 | ## Getting Started 41 | 42 | Just pull and run our docker image, and you're ready to use 🎉 43 | 44 | ```sh 45 | docker run -i -v $(pwd):/workspace ghcr.io/flatt-security/shisho-cli:latest 46 | ``` 47 | 48 | See [Getting Started](/shisho/getting-started) to learn Shisho more. 49 | 50 | ## Strengths 51 | 52 | Shisho has mainly two strengths: **it runs everywhere**, and **it runs extremely fast**. 53 | 54 | > 📝 We already have `sed` or something like that. There are already several static analysis engines in the world indeed. Now you may wonder why do we need Shisho now. 55 | 56 | ### 1. Run Extremely Fast 57 | 58 | In addition, **Shisho runs everywhere**! You can use this tool offline so that you don't need to transfer your code anywhere. One can use Shisho inside Continuous Integration (CI) systems like GitHub Actions. 59 | 60 | ### 2. Run Everywhere 61 | 62 | Another key aspect of Shisho is **speed**; it runs so fast with the help of [Rust](https://www.rust-lang.org)! 63 | 64 | ## Language Support 65 | 66 | The current support language is: 67 | 1. Terrafrom 68 | 2. Go 69 | 3. Dockerfile 70 | 71 | See [the roadmap](/roadmap) for further details. You can request new language support at [GitHub issues](https://github.com/flatt-security/shisho/issues)! 72 | 73 | ## Shisho Playground 74 | 75 | We provide a test environment to execute your own Shisho rules. Please check [Shisho Playground](https://play.shisho.dev/). 76 | 77 | ## Feedback 78 | 79 | We'd love to hear your feedback! Feel free to ask Shisho team anything at [GitHub issues](https://github.com/flatt-security/shisho/issues). 80 | -------------------------------------------------------------------------------- /docs/src/components/styles/PageNavigationButtons.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const StyledNextPrevious = styled('div')` 4 | margin: 0px; 5 | padding: 0px; 6 | width: auto; 7 | display: grid; 8 | grid-template-rows: auto; 9 | column-gap: 24px; 10 | grid-template-columns: calc(50% - 8px) calc(50% - 8px); 11 | 12 | .previousBtn { 13 | cursor: pointer; 14 | -moz-box-align: center; 15 | -moz-box-direction: normal; 16 | -moz-box-orient: horizontal; 17 | margin: 0px; 18 | padding: 0px; 19 | position: relative; 20 | display: flex; 21 | flex-direction: row; 22 | align-items: center; 23 | place-self: stretch; 24 | border-radius: 3px; 25 | border: 1px solid rgb(230, 236, 241); 26 | transition: border 200ms ease 0s; 27 | box-shadow: rgba(116, 129, 141, 0.1) 0px 3px 8px 0px; 28 | text-decoration: none; 29 | 30 | background-color: ${props => props.theme.colors.background}; 31 | color: ${props => props.theme.colors.text}; 32 | } 33 | 34 | .nextBtn { 35 | cursor: pointer; 36 | -moz-box-align: center; 37 | -moz-box-direction: normal; 38 | -moz-box-orient: horizontal; 39 | margin: 0px; 40 | padding: 0px; 41 | position: relative; 42 | display: flex; 43 | flex-direction: row; 44 | align-items: center; 45 | place-self: stretch; 46 | border-radius: 3px; 47 | border: 1px solid rgb(230, 236, 241); 48 | transition: border 200ms ease 0s; 49 | box-shadow: rgba(116, 129, 141, 0.1) 0px 3px 8px 0px; 50 | text-decoration: none; 51 | 52 | background-color: ${props => props.theme.colors.background}; 53 | color: ${props => props.theme.colors.text}; 54 | } 55 | 56 | .nextBtn:hover, 57 | .previousBtn:hover { 58 | text-decoration: none; 59 | border: 1px solid #1ed3c6; 60 | } 61 | 62 | .nextBtn:hover .rightArrow, 63 | .previousBtn:hover .leftArrow { 64 | color: #1ed3c6; 65 | } 66 | 67 | .leftArrow { 68 | display: flex; 69 | margin: 0px; 70 | color: rgb(157, 170, 182); 71 | flex: 0 0 auto; 72 | font-size: 24px; 73 | transition: color 200ms ease 0s; 74 | padding: 16px; 75 | padding-right: 16px; 76 | } 77 | 78 | .rightArrow { 79 | display: flex; 80 | flex: 0 0 auto; 81 | font-size: 24px; 82 | transition: color 200ms ease 0s; 83 | padding: 16px; 84 | padding-left: 16px; 85 | margin: 0px; 86 | color: rgb(157, 170, 182); 87 | } 88 | 89 | .nextPreviousTitle { 90 | display: block; 91 | margin: 0px; 92 | padding: 0px; 93 | transition: color 200ms ease 0s; 94 | } 95 | 96 | .nextPreviousTitle span { 97 | font-size: 16px; 98 | line-height: 1.5; 99 | font-weight: 500; 100 | } 101 | 102 | @media (max-width: 767px) { 103 | display: block; 104 | padding: 0 15px; 105 | 106 | .previousBtn { 107 | margin-bottom: 20px; 108 | } 109 | } 110 | `; 111 | -------------------------------------------------------------------------------- /docs/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const componentWithMDXScope = require('gatsby-plugin-mdx/component-with-mdx-scope'); 2 | 3 | const path = require('path'); 4 | 5 | const startCase = require('lodash.startcase'); 6 | 7 | const config = require('./config'); 8 | 9 | exports.createPages = ({ graphql, actions }) => { 10 | const { createPage } = actions; 11 | 12 | return new Promise((resolve, reject) => { 13 | resolve( 14 | graphql( 15 | ` 16 | { 17 | allMdx { 18 | edges { 19 | node { 20 | fields { 21 | id 22 | } 23 | tableOfContents 24 | fields { 25 | slug 26 | } 27 | } 28 | } 29 | } 30 | } 31 | ` 32 | ).then(result => { 33 | if (result.errors) { 34 | console.log(result.errors); // eslint-disable-line no-console 35 | reject(result.errors); 36 | } 37 | 38 | // Create blog posts pages. 39 | result.data.allMdx.edges.forEach(({ node }) => { 40 | createPage({ 41 | path: node.fields.slug ? node.fields.slug : '/', 42 | component: path.resolve('./src/templates/docs.js'), 43 | context: { 44 | id: node.fields.id, 45 | }, 46 | }); 47 | }); 48 | }) 49 | ); 50 | }); 51 | }; 52 | 53 | exports.onCreateWebpackConfig = ({ actions }) => { 54 | actions.setWebpackConfig({ 55 | resolve: { 56 | modules: [path.resolve(__dirname, 'src'), 'node_modules'], 57 | alias: { 58 | $components: path.resolve(__dirname, 'src/components'), 59 | buble: '@philpl/buble', // to reduce bundle size 60 | }, 61 | }, 62 | }); 63 | }; 64 | 65 | exports.onCreateBabelConfig = ({ actions }) => { 66 | actions.setBabelPlugin({ 67 | name: '@babel/plugin-proposal-export-default-from', 68 | }); 69 | }; 70 | 71 | exports.onCreateNode = ({ node, getNode, actions }) => { 72 | const { createNodeField } = actions; 73 | 74 | if (node.internal.type === `Mdx`) { 75 | const parent = getNode(node.parent); 76 | 77 | let value = parent.relativePath.replace(parent.ext, ''); 78 | 79 | if (value === 'index') { 80 | value = ''; 81 | } 82 | 83 | if (config.gatsby && config.gatsby.trailingSlash) { 84 | createNodeField({ 85 | name: `slug`, 86 | node, 87 | value: value === '' ? `/` : `/${value}/`, 88 | }); 89 | } else { 90 | createNodeField({ 91 | name: `slug`, 92 | node, 93 | value: `/${value}`, 94 | }); 95 | } 96 | 97 | createNodeField({ 98 | name: 'id', 99 | node, 100 | value: node.id, 101 | }); 102 | 103 | createNodeField({ 104 | name: 'title', 105 | node, 106 | value: node.frontmatter.title || startCase(parent.name), 107 | }); 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /src/core/source.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::core::language::Queryable; 4 | use std::marker::PhantomData; 5 | 6 | use super::{matcher::MatchedItem, rewriter::RewriteOption}; 7 | 8 | #[derive(Clone)] 9 | pub struct Code<L> 10 | where 11 | L: Queryable, 12 | { 13 | code: String, 14 | _marker: PhantomData<L>, 15 | } 16 | 17 | impl<T> Code<T> 18 | where 19 | T: Queryable, 20 | { 21 | pub fn as_str(&self) -> &str { 22 | self.code.as_str() 23 | } 24 | } 25 | 26 | impl<T> Code<T> 27 | where 28 | T: Queryable, 29 | { 30 | pub fn to_rewritten_form(self, item: &MatchedItem, roption: RewriteOption<T>) -> Result<Self> { 31 | let current_code = self.as_str().as_bytes(); 32 | 33 | let before_snippet = String::from_utf8(current_code[0..item.area.start_byte()].to_vec())?; 34 | let snippet = roption.to_rewritten_snippet(item)?; 35 | let after_snippet = String::from_utf8( 36 | current_code[item.area.end_byte().min(current_code.len())..current_code.len()].to_vec(), 37 | )?; 38 | 39 | Ok(Code::from(format!( 40 | "{}{}{}", 41 | before_snippet, snippet, after_snippet 42 | ))) 43 | } 44 | } 45 | 46 | impl<T> AsRef<str> for Code<T> 47 | where 48 | T: Queryable, 49 | { 50 | fn as_ref(&self) -> &str { 51 | self.code.as_str() 52 | } 53 | } 54 | 55 | impl<T, C> From<T> for Code<C> 56 | where 57 | T: Into<String>, 58 | C: Queryable, 59 | { 60 | fn from(code: T) -> Self { 61 | Self { 62 | code: code.into(), 63 | _marker: PhantomData, 64 | } 65 | } 66 | } 67 | 68 | #[derive(Clone)] 69 | pub struct NormalizedSource { 70 | pub source: Vec<u8>, 71 | with_extra_newline: bool, 72 | } 73 | 74 | impl NormalizedSource { 75 | #[inline] 76 | pub fn with_extra_newline(&self) -> bool { 77 | self.with_extra_newline 78 | } 79 | } 80 | 81 | impl AsRef<[u8]> for NormalizedSource { 82 | fn as_ref(&self) -> &[u8] { 83 | &self.source 84 | } 85 | } 86 | 87 | impl From<NormalizedSource> for Vec<u8> { 88 | fn from(n: NormalizedSource) -> Self { 89 | n.source 90 | } 91 | } 92 | 93 | impl From<&[u8]> for NormalizedSource { 94 | fn from(source: &[u8]) -> Self { 95 | if !source.is_empty() && source[source.len() - 1] != b'\n' { 96 | Self { 97 | source: [source, "\n".as_bytes()].concat(), 98 | with_extra_newline: true, 99 | } 100 | } else { 101 | Self { 102 | source: source.into(), 103 | with_extra_newline: false, 104 | } 105 | } 106 | } 107 | } 108 | 109 | impl From<String> for NormalizedSource { 110 | fn from(source: String) -> Self { 111 | source.as_bytes().into() 112 | } 113 | } 114 | 115 | impl From<&str> for NormalizedSource { 116 | fn from(source: &str) -> Self { 117 | source.as_bytes().into() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /docs/content/shisho-cloud/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Getting Started' 3 | metaTitle: 'Getting Started - Shisho Cloud' 4 | metaDescription: 'This page describes details of Shisho Cloud preparation.' 5 | --- 6 | 7 | ## Overview 8 | 9 | [Shisho Cloud](https://shisho.dev/) supports you in monitoring your [Terrafrom](https://www.terraform.io/) code of cloud resource management to prevent security matters. All you need to do is just three steps: 10 | 11 | 1. Sign up 12 | 2. Connect with repository managers 13 | 3. Link with repositories 14 | 15 | ### 1. Sign up 16 | 17 | [Shisho Cloud](https://shisho.dev/) supports three SSO, [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/) and [BitBucket](https://bitbucket.org/product). Yes, this equals the currently supported repository managers. It might be better to sign up with one of them, which is your preferred repository connection. For instance, if you want to connect with one of [GitHub](https://github.com/) repositories, you should sign up with [GitHub](https://github.com/) SSO. After sign-up, let's start the registration with easy steps! 18 | 19 | <img src="/images/sso.png" alt="sso screenshot" width="400"/> 20 | 21 | ### 2. Select repository manager 22 | 23 | First of all, you need to select one of the repository connections. The currently supported services are: 24 | 25 | 1. [GitHub](https://github.com/) 26 | 2. [GitLab](https://about.gitlab.com/) 27 | 3. [BitBucket](https://bitbucket.org/product) 28 | 29 | > 📝 Tips: For [GitHub](https://github.com/) users, you need to install the Shisho GitHub App, which is one of the official "GitHub Apps". It supports integrating with [Shisho Cloud](https://shisho.dev/) and managing access permissions. If you have any questions, please check "Shisho GitHub App" on the page [Frequently asked questions](/shisho-cloud/frequently-asked-questions) for further details. 30 | 31 | ### 3. Select repository 32 | 33 | Please select a target repository that [Shisho Cloud](https://shisho.dev/) monitors your [Terrafrom](https://www.terraform.io/) code to maintain your healthy cloud resources. If you do not have [Terrafrom](https://www.terraform.io/) code OR you want to test [Shisho Cloud](https://shisho.dev/) without your own repositories, please folk and select a provided test repository. The [Terrafrom](https://www.terraform.io/) code in the test repository misconfigures [AWS](https://aws.amazon.com/) resources and policies for dummy resources on purpose. We assume it is enough to demonstrate the performance of [Shisho Cloud](https://shisho.dev/). 34 | 35 | > 📝 Tips: If you have some questions about the test repository, please check the section "Test repository [flatt-security/tfgoat-aws](https://github.com/flatt-security/tfgoat-aws)" on the page [Frequently asked questions](/shisho-cloud/frequently-asked-questions) 36 | 37 | That's all you have to do. Let's work and develop as usual with [Terrafrom](https://www.terraform.io/) code which is monitored by [Shisho Cloud](https://shisho.dev/)! 38 | 39 | ## Do you have any questions? 40 | 41 | Please check the page [Frequently asked questions](/shisho-cloud/frequently-asked-questions) 42 | 43 | -------------------------------------------------------------------------------- /src/cli/tests/ruleset/generic/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | ruleset_test! { 4 | encoding: [ 5 | ("ruleset.yaml", "shift_jis.go", Result::Ok(3), Some(encoding_rs::SHIFT_JIS)), 6 | ("ruleset.yaml", "utf_16le.go", Result::Ok(3), Some(encoding_rs::UTF_16LE)), 7 | ], 8 | nested_constraints: [ 9 | ("ruleset.yaml", "match.tf", Result::Ok(1), None), 10 | ("ruleset.yaml", "unmatch.with-inner.tf", Result::Ok(0), None), 11 | ("ruleset.yaml", "unmatch.without-inner.tf", Result::Ok(0), None), 12 | ], 13 | constraints: [ 14 | ("be-any-of.yaml", "match.tf", Result::Ok(1), None), 15 | ("be-any-of.yaml", "unmatch.tf", Result::Ok(0), None), 16 | ("not-be-any-of.yaml", "match.tf", Result::Ok(1), None), 17 | ("not-be-any-of.yaml", "unmatch.tf", Result::Ok(0), None), 18 | 19 | ("match-pattern.yaml", "match.tf", Result::Ok(1), None), 20 | ("match-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 21 | ("not-match-pattern.yaml", "match.tf", Result::Ok(1), None), 22 | ("not-match-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 23 | 24 | ("match-regex-pattern.yaml", "match.tf", Result::Ok(1), None), 25 | ("match-regex-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 26 | ("not-match-regex-pattern.yaml", "match.tf", Result::Ok(1), None), 27 | ("not-match-regex-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 28 | 29 | ("match-any-of-pattern.yaml", "match.tf", Result::Ok(1), None), 30 | ("match-any-of-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 31 | ("not-match-any-of-pattern.yaml", "match.tf", Result::Ok(1), None), 32 | ("not-match-any-of-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 33 | 34 | ("match-any-of-regex-pattern.yaml", "match.tf", Result::Ok(1), None), 35 | ("match-any-of-regex-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 36 | ("not-match-any-of-regex-pattern.yaml", "match.tf", Result::Ok(1), None), 37 | ("not-match-any-of-regex-pattern.yaml", "unmatch.tf", Result::Ok(0), None), 38 | ], 39 | invalid_constraints: [ 40 | ("invalid-match-string.yaml", "unmatch.tf", Result::Err(anyhow::anyhow!("")), None), 41 | ("invalid-match-strings.yaml", "unmatch.tf", Result::Err(anyhow::anyhow!("")), None), 42 | 43 | ("ambiguous-pattern-use.yaml", "unmatch.tf", Result::Err(anyhow::anyhow!("")), None), 44 | ("ambiguous-regex-pattern-use.yaml", "unmatch.tf", Result::Err(anyhow::anyhow!("")), None), 45 | 46 | ("mixed-pattern-like.yaml", "unmatch.tf", Result::Err(anyhow::anyhow!("")), None), 47 | ("no-pattern-like.yaml", "unmatch.tf", Result::Err(anyhow::anyhow!("")), None), 48 | ], 49 | shared_constraints: [ 50 | ("ruleset.yaml", "test.Dockerfile", Result::Ok(8), None), 51 | ("ruleset.yaml", "dockerfile", Result::Ok(8), None), 52 | ("ruleset.yaml", "Dockerfile.test", Result::Ok(8), None), 53 | ], 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shisho 2 | 3 | ![shisho](./docs/public/images/header.png) 4 | 5 | [![GitHub Release][release-img]][release] 6 | [![GitHub Marketplace][marketplace-img]][marketplace] 7 | [![License][license-img]][license] 8 | [![Documentation][documentation-img]][documentation] 9 | [![Test][test-img]][test] 10 | [![Playground][playground-img]][playground] 11 | 12 | Shisho is a lightweight static analyzer for developers. 13 | 14 | ### Please see [the usage documentation](https://docs.shisho.dev) for further information. 15 | 16 | ![demo](./docs/content/images/shisho-demo.gif) 17 | 18 | ## Try at Playground 19 | 20 | You can try Shisho at [our playground](https://play.shisho.dev). 21 | 22 | ## Try with Docker 23 | 24 | You can try shisho in your machine as follows: 25 | 26 | ```sh 27 | echo "func test(v []string) int { return len(v) + 1; }" | docker run -i ghcr.io/flatt-security/shisho-cli:latest find "len(:[...])" --lang=go 28 | ``` 29 | 30 | ```sh 31 | echo "func test(v []string) int { return len(v) + 1; }" > file.go 32 | docker run -i -v $(pwd):/workspace ghcr.io/flatt-security/shisho-cli:latest find "len(:[...])" --lang=go /workspace/file.go 33 | ``` 34 | 35 | ## Install with pre-built binaries 36 | 37 | When you'd like to run shisho outside docker containers, please follow the instructions below: 38 | 39 | ### Linux / macOS 40 | 41 | Run the following command(s): 42 | 43 | ```sh 44 | # Linux 45 | wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-unknown-linux-gnu.zip -O shisho.zip 46 | unzip shisho.zip 47 | chmod +x ./shisho 48 | mv ./shisho /usr/local/bin/shisho 49 | 50 | # macOS 51 | wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-apple-darwin.zip -O shisho.zip 52 | unzip shisho.zip 53 | chmod +x ./shisho 54 | mv ./shisho /usr/local/bin/shisho 55 | ``` 56 | 57 | Then you'll see a shisho's executable in `/usr/local/bin`. 58 | 59 | ### Windows 60 | 61 | Download the prebuild binary from [releases](https://github.com/flatt-security/shisho/releases) and put it into your `%PATH%` directory. 62 | 63 | If you're using [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10), you can install shisho with the above instructions. 64 | 65 | # More 66 | 67 | - We're also building [Shisho as a Service](https://shisho.dev) to make Security-as-Code more accessible. 68 | - If you need direct support, you can contact us at `contact@flatt.tech`. 69 | 70 | [release]: https://github.com/flatt-security/shisho/releases/latest 71 | [release-img]: https://img.shields.io/github/release/flatt-security/shisho.svg?logo=github 72 | [marketplace]: https://github.com/marketplace/actions/shisho-action 73 | [marketplace-img]: https://img.shields.io/badge/marketplace-shisho--action-blue?logo=github 74 | [license]: https://github.com/flatt-security/shisho/blob/main/LICENSE 75 | [license-img]: https://img.shields.io/github/license/flatt-security/shisho 76 | [documentation]: https://docs.shisho.dev 77 | [documentation-img]: https://img.shields.io/badge/docs-docs.shisho.dev-purple 78 | [playground]: https://play.shisho.dev 79 | [playground-img]: https://img.shields.io/badge/playground-playground.shisho.dev-purple 80 | [test]: https://github.com/flatt-security/shisho/actions/workflows/test.yml 81 | [test-img]: https://github.com/flatt-security/shisho/actions/workflows/test.yml/badge.svg?branch=main 82 | -------------------------------------------------------------------------------- /src/core/pattern.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | constraint::Constraint, language::Queryable, node::RootNode, 3 | ruleset::RawPatternWithConstraints, source::NormalizedSource, 4 | }; 5 | use anyhow::{anyhow, Result}; 6 | use std::{ 7 | convert::{TryFrom, TryInto}, 8 | marker::PhantomData, 9 | }; 10 | 11 | #[derive(Debug)] 12 | pub struct Pattern<T> 13 | where 14 | T: Queryable, 15 | { 16 | pub source: Vec<u8>, 17 | with_extra_newline: bool, 18 | 19 | tstree: tree_sitter::Tree, 20 | _marker: PhantomData<T>, 21 | } 22 | 23 | impl<T> Pattern<T> 24 | where 25 | T: Queryable, 26 | { 27 | pub fn to_root_node(&'_ self) -> RootNode<'_> { 28 | RootNode::from_tstree(&self.tstree, &self.source, self.with_extra_newline) 29 | } 30 | 31 | #[inline] 32 | pub fn string_between(&self, start: usize, end: usize) -> Result<String> { 33 | let start = if self.source.len() == start && self.with_extra_newline { 34 | start - 1 35 | } else { 36 | start 37 | }; 38 | let end = if self.source.len() == end && self.with_extra_newline { 39 | end - 1 40 | } else { 41 | end 42 | }; 43 | Ok(String::from_utf8(self.source[start..end].to_vec())?) 44 | } 45 | } 46 | 47 | impl<T> TryFrom<NormalizedSource> for Pattern<T> 48 | where 49 | T: Queryable, 50 | { 51 | type Error = anyhow::Error; 52 | 53 | fn try_from(source: NormalizedSource) -> Result<Self, anyhow::Error> { 54 | let mut parser = tree_sitter::Parser::new(); 55 | parser.set_language(T::query_language())?; 56 | 57 | let tstree = parser 58 | .parse(source.as_ref(), None) 59 | .ok_or(anyhow!("failed to load the code"))?; 60 | 61 | let with_extra_newline = source.with_extra_newline(); 62 | Ok(Pattern { 63 | source: source.into(), 64 | with_extra_newline, 65 | 66 | tstree, 67 | _marker: PhantomData, 68 | }) 69 | } 70 | } 71 | 72 | impl<T> TryFrom<&str> for Pattern<T> 73 | where 74 | T: Queryable, 75 | { 76 | type Error = anyhow::Error; 77 | 78 | fn try_from(source: &str) -> Result<Self, anyhow::Error> { 79 | let source = NormalizedSource::from(source); 80 | source.try_into() 81 | } 82 | } 83 | 84 | #[derive(Debug)] 85 | pub struct PatternWithConstraints<T: Queryable> { 86 | pub pattern: Pattern<T>, 87 | pub constraints: Vec<Constraint<T>>, 88 | } 89 | 90 | impl<T: Queryable> PatternWithConstraints<T> { 91 | pub fn new(pattern: Pattern<T>, constraints: Vec<Constraint<T>>) -> Self { 92 | Self { 93 | pattern, 94 | constraints, 95 | } 96 | } 97 | } 98 | 99 | impl<T: Queryable> TryFrom<RawPatternWithConstraints> for PatternWithConstraints<T> { 100 | type Error = anyhow::Error; 101 | 102 | fn try_from(rpc: RawPatternWithConstraints) -> Result<Self> { 103 | let pattern = Pattern::<T>::try_from(rpc.pattern.as_str())?; 104 | let constraints = rpc 105 | .constraints 106 | .iter() 107 | .map(|x| Constraint::try_from(x.clone())) 108 | .collect::<Result<Vec<Constraint<T>>>>()?; 109 | Ok(Self { 110 | pattern, 111 | constraints, 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/cli/subcommand/find.rs: -------------------------------------------------------------------------------- 1 | //! This module defines `check` subcommand. 2 | 3 | use crate::cli::encoding::{parse_encoding, LABELS_SORTED}; 4 | use crate::cli::reporter::{ConsoleReporter, JSONReporter, Reporter, ReporterType, SARIFReporter}; 5 | use crate::cli::{subcommand::check::handle_rulemap, CommonOpts, ReportOpts}; 6 | use crate::core::ruleset::{self, RawPatternWithConstraints, Rule}; 7 | use ansi_term::Color; 8 | use anyhow::Result; 9 | use encoding_rs::Encoding; 10 | use std::{array::IntoIter, collections::HashMap, path::PathBuf}; 11 | use structopt::StructOpt; 12 | 13 | /// Checks files with a pattern given in command line arguments 14 | #[derive(StructOpt, Debug)] 15 | pub struct FindOpts { 16 | /// Code pattern for searching 17 | #[structopt()] 18 | pub pattern: String, 19 | 20 | /// File path to search 21 | #[structopt(parse(from_os_str))] 22 | pub target_path: Option<PathBuf>, 23 | 24 | /// Language name to use 25 | #[structopt(short, long)] 26 | pub lang: ruleset::Language, 27 | 28 | /// Rewriting pattern 29 | #[structopt(long)] 30 | pub rewrite: Option<String>, 31 | 32 | #[structopt(flatten)] 33 | pub common: CommonOpts, 34 | 35 | #[structopt(short, long, parse(try_from_str = parse_encoding), possible_values(&LABELS_SORTED))] 36 | pub encoding: Option<&'static Encoding>, 37 | 38 | #[structopt(long)] 39 | pub exit_zero: bool, 40 | 41 | #[structopt(flatten)] 42 | pub report: ReportOpts, 43 | 44 | #[structopt(long)] 45 | pub exclude: Vec<String>, 46 | } 47 | 48 | pub fn run(opts: FindOpts) -> i32 { 49 | let exit_zero = opts.exit_zero; 50 | match handle_opts(opts) { 51 | Ok(total_findings) => { 52 | if total_findings > 0 && !exit_zero { 53 | 1 54 | } else { 55 | 0 56 | } 57 | } 58 | Err(e) => { 59 | eprintln!("{}: {}", Color::Red.paint("error"), e); 60 | 1 61 | } 62 | } 63 | } 64 | 65 | fn handle_opts(opts: FindOpts) -> Result<usize> { 66 | let rule = Rule::new( 67 | "inline".into(), 68 | opts.lang, 69 | "matched with the given rule".into(), 70 | vec![RawPatternWithConstraints { 71 | pattern: opts.pattern, 72 | constraints: vec![], 73 | }], 74 | opts.rewrite.map_or(vec![], |x| vec![x]), 75 | vec![], 76 | ); 77 | 78 | let rule_map = 79 | IntoIter::new([(opts.lang, vec![rule])]).collect::<HashMap<ruleset::Language, Vec<Rule>>>(); 80 | 81 | let stdout = std::io::stdout(); 82 | let mut stdout = stdout.lock(); 83 | match opts.report.format { 84 | ReporterType::JSON => handle_rulemap( 85 | JSONReporter::new(&mut stdout), 86 | opts.target_path, 87 | opts.exclude, 88 | opts.encoding, 89 | rule_map, 90 | ), 91 | ReporterType::Console => handle_rulemap( 92 | ConsoleReporter::new(&mut stdout), 93 | opts.target_path, 94 | opts.exclude, 95 | opts.encoding, 96 | rule_map, 97 | ), 98 | ReporterType::SARIF => handle_rulemap( 99 | SARIFReporter::new(&mut stdout), 100 | opts.target_path, 101 | opts.exclude, 102 | opts.encoding, 103 | rule_map, 104 | ), 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /docs/content/shisho/learn-shisho/02-rule.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Rule' 3 | metaTitle: '02 - Rule' 4 | metaDescription: 'This page describes details of rules for pattern matching.' 5 | --- 6 | 7 | ## Overview 8 | 9 | _A rule_ describes how matched parts for a pattern should be treated. It mainly consists of: 10 | 11 | - an ID 12 | - [one or more patterns](/shisho/learn-shisho/01-pattern) 13 | - a target language name of the pattern 14 | - a message related to the pattern 15 | - [rule constraints](/shisho/learn-shisho/03-constraint) (optional) 16 | - [one or more rewrite patterns](/shisho/learn-shisho/04-rewrite-option) (optional) 17 | 18 | _A rule set_ is a set of rules with Shisho's version information. Here's an example ruleset: 19 | 20 | ```yaml 21 | version: '1' 22 | rules: 23 | - id: sample-policy 24 | language: hcl 25 | pattern: | 26 | size = :[X] 27 | message: | 28 | here comes your own message 29 | rewrite: size = 20 30 | ``` 31 | 32 | ## Properties 33 | 34 | This section explains basic properties. 35 | 36 | ### id 37 | 38 | You can set an id whatever you want. However, we recommend: 39 | - Unique 40 | - Meaningful 41 | - Easy to understand the policy 42 | 43 | ### language 44 | 45 | This is a target language and currently available languages are: 46 | - hcl 47 | - go 48 | - dockerfile 49 | 50 | Last Update: 10/21/2021 51 | 52 | ### message 53 | 54 | A message is displayed when it matches `pattern` block. 55 | 56 | ### pattern and patterns 57 | 58 | _A pattern_ describes what parts are searched and you can select single pattern **OR** multiple patterns. 59 | 60 | #### Single Pattern 61 | 62 | A below example is a fundamental usage. This searches the part `auto_recovery = false` in `resource "foobar"`. 63 | 64 | ```yaml 65 | pattern: | 66 | resource "foobar" :[NAME] { 67 | :[...X] 68 | auto_recovery = false 69 | :[...Y] 70 | } 71 | ``` 72 | 73 | > 📝 Tips: what is `:[...X]` and `:[...Y]`? 74 | > These are _metavariables_. Please review the section, _Metavariable_ on the page [Pattern](/shisho/learn-shisho/01-pattern). 75 | 76 | #### Multiple Patterns 77 | 78 | Multiple patterns are available for complex searches. For instance, the below patterns search the parts to meet **either** case, `risk_level` is `1` **OR** `2`. 79 | 80 | ```yaml 81 | patterns: 82 | - pattern: | 83 | resource "foobar" :[NAME] { 84 | :[...X] 85 | risk_level = 1 86 | :[...Y] 87 | } 88 | - pattern: | 89 | resource "foobar" :[NAME] { 90 | :[...X] 91 | risk_level = 2 92 | :[...Y] 93 | } 94 | ``` 95 | 96 | #### Invalid Pattern Expression 97 | 98 | You can select **either** single or multiple patterns. Your rule cannot have both expressions. 99 | 100 | ```yaml 101 | 102 | // This is an invalid example because the code has both `pattern` and `patterns`. 103 | // You need explicitly select either one. 104 | pattern: | 105 | resource "foobar" :[NAME] { 106 | :[...X] 107 | risk_level = 1 108 | :[...Y] 109 | } 110 | patterns: 111 | - pattern: | 112 | resource "foobar" :[NAME] { 113 | :[...X] 114 | risk_level = 2 115 | :[...Y] 116 | } 117 | - pattern: | 118 | resource "foobar" :[NAME] { 119 | :[...X] 120 | risk_level = 3 121 | :[...Y] 122 | } 123 | ``` 124 | 125 | ### rewrite and rewrite_options 126 | 127 | If the parts match a `pattern` block, it is transformed by a `rewrite` block. You can utilize a single rewrite option with the `rewrite` block in a rule **OR** multiple rewrite options with a `rewrite_options` block. Please check the further details on the page [one or more rewrite patterns](/shisho/learn-shisho/04-rewrite-option). 128 | -------------------------------------------------------------------------------- /docs/src/components/sidebar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tree from './tree'; 3 | import { StaticQuery, graphql } from 'gatsby'; 4 | import styled from '@emotion/styled'; 5 | import { ExternalLink } from 'react-feather'; 6 | import config from '../../../config'; 7 | 8 | // eslint-disable-next-line no-unused-vars 9 | const ListItem = styled(({ className, active, level, ...props }) => { 10 | return ( 11 | <li className={className}> 12 | <a href={props.to} {...props} target="_blank" rel="noopener noreferrer"> 13 | {props.children} 14 | </a> 15 | </li> 16 | ); 17 | })` 18 | list-style: none; 19 | 20 | a { 21 | color: #5c6975; 22 | text-decoration: none; 23 | font-weight: ${({ level }) => (level === 0 ? 700 : 400)}; 24 | padding: 0.45rem 0 0.45rem ${props => 2 + (props.level || 0) * 1}rem; 25 | display: block; 26 | position: relative; 27 | 28 | &:hover { 29 | color: #1ed3c6 !important; 30 | } 31 | 32 | ${props => 33 | props.active && 34 | ` 35 | // color: #663399; 36 | border-color: rgb(230,236,241) !important; 37 | border-style: solid none solid solid; 38 | border-width: 1px 0px 1px 1px; 39 | background-color: #fff; 40 | `} // external link icon 41 | svg { 42 | float: right; 43 | margin-right: 1rem; 44 | } 45 | } 46 | `; 47 | 48 | const Sidebar = styled('aside')` 49 | width: 100%; 50 | height: 100vh; 51 | overflow: auto; 52 | position: fixed; 53 | padding-left: 0px; 54 | position: -webkit-sticky; 55 | position: -moz-sticky; 56 | position: sticky; 57 | top: 0; 58 | padding-right: 0; 59 | -webkit-box-shadow: -1px 0px 4px 1px rgba(175, 158, 232, 0.4); 60 | 61 | @media only screen and (max-width: 1023px) { 62 | width: 100%; 63 | /* position: relative; */ 64 | height: 100vh; 65 | } 66 | 67 | @media (min-width: 767px) and (max-width: 1023px) { 68 | padding-left: 0; 69 | } 70 | 71 | @media only screen and (max-width: 767px) { 72 | padding-left: 0px; 73 | height: auto; 74 | } 75 | `; 76 | 77 | const Divider = styled(props => ( 78 | <li {...props}> 79 | <hr /> 80 | </li> 81 | ))` 82 | list-style: none; 83 | padding: 0.5rem 0; 84 | 85 | hr { 86 | margin: 0; 87 | padding: 0; 88 | border: 0; 89 | border-bottom: 1px solid #ede7f3; 90 | } 91 | `; 92 | 93 | const SidebarLayout = ({ location }) => ( 94 | <StaticQuery 95 | query={graphql` 96 | query { 97 | allMdx { 98 | edges { 99 | node { 100 | fields { 101 | slug 102 | title 103 | } 104 | } 105 | } 106 | } 107 | } 108 | `} 109 | render={({ allMdx }) => { 110 | return ( 111 | <Sidebar> 112 | {config.sidebar.title ? ( 113 | <div 114 | className={'sidebarTitle hiddenMobile'} 115 | dangerouslySetInnerHTML={{ __html: config.sidebar.title }} 116 | /> 117 | ) : null} 118 | <ul className={'sideBarUL'}> 119 | <Tree edges={allMdx.edges} /> 120 | {config.sidebar.links && config.sidebar.links.length > 0 && <Divider />} 121 | {config.sidebar.links.map((link, key) => { 122 | if (link.link !== '' && link.text !== '') { 123 | return ( 124 | <ListItem key={key} to={link.link}> 125 | {link.text} 126 | <ExternalLink size={14} /> 127 | </ListItem> 128 | ); 129 | } 130 | })} 131 | </ul> 132 | </Sidebar> 133 | ); 134 | }} 135 | /> 136 | ); 137 | 138 | export default SidebarLayout; 139 | -------------------------------------------------------------------------------- /docs/content/shisho/learn-shisho/01-pattern.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Pattern' 3 | metaTitle: '01 - Pattern' 4 | metaDescription: "This page describes details of Shisho's DSL for pattern matching." 5 | --- 6 | 7 | ## Overview 8 | 9 | _A pattern_ describes the code to search for. The following string is an example of patterns for HCL which matches any `aws_ebs_volume` resource with any name and any configuration arguments: 10 | 11 | ``` 12 | resource "aws_ebs_volume" :[NAME] { 13 | :[...X] 14 | } 15 | ``` 16 | 17 | ## Metavariable 18 | 19 | In this example, `:[NAME]` behaves like a [capture group](https://www.regular-expressions.info/brackets.html) in regular expressions; it matches **one** expression, identifier, or block, saving the matched part for use in code transformation. In general this notation is called _metavariables_. 20 | 21 | ## Ellipsis Metavariable 22 | 23 | `:[...X]` behaves almost same as `:[X]`, but it matches **zero or more** expressions, identifiers, or blocks. This notation is called _ellipsis metavariables_. 24 | 25 | ## Search with Metavariables 26 | 27 | By using the same metavariable multiple times, you can search over your code, guaranteeing the equality of all the matched parts for the metavariable. For example, consider the following pattern: 28 | 29 | ``` 30 | attr1 = :[X] 31 | attr2 = :[X] 32 | ``` 33 | 34 | This pattern DOES match (1) while it does NOT match (2): 35 | 36 | ``` 37 | // (1) 38 | resource "hoge" "foo" { 39 | attr1 = 1 40 | attr2 = 1 41 | } 42 | 43 | // (2) 44 | resource "hoge" "foo" { 45 | attr1 = 1 46 | attr2 = 2 47 | } 48 | ``` 49 | 50 | > 📝 Tips: `:[_]` is called _anonymous metavariable_. The equality of the matched parts for `:[_]` will NOT be guaranteed; the following pattern matches both of (1) and (2). 51 | > 52 | > ``` 53 | > attr1 = :[_] 54 | > attr2 = :[_] 55 | > ``` 56 | > 57 | > Similarly, `:[...]` is called _anonymous ellipsis metavariable_, whose matched parts won't be tested for equivalence. 58 | 59 | ## Pattern Usage 60 | 61 | The above sections explain the base parameters and their principles so far. Let's begin with more specific cases depends on your target language! 62 | 63 | ### Pattern in HCL 64 | 65 | Let's check the case of HCL code (e.g. Terraform code). Please execute below `shisho find 'auto_repair = :[X]' ...`. This searches whether the target `resource "google_container_node_pool" ...` includes the `auto_repair` attribute with any values. 66 | 67 | ```shell 68 | $ shisho find 'auto_repair = :[X]' --lang=hcl << EOF 69 | resource "google_container_node_pool" "bad_example" { 70 | name = "example-node-pool" 71 | cluster = google_container_cluster.primary.id 72 | management { 73 | auto_repair = false 74 | } 75 | } 76 | EOF 77 | ``` 78 | 79 | The expected result is below. 80 | 81 | ``` 82 | [inline]: matched with the given rule 83 | In /dev/stdin: 84 | | 85 | 5 | auto_repair = false 86 | | 87 | ``` 88 | 89 | ### Pattern in Go 90 | 91 | Please execute a simple below pattern `shisho find 'len(:[...])' ...`. This searches whether the target `func test(...` includes the code `len()` with any inside values. 92 | 93 | ```shell 94 | $ shisho find 'len(:[...])' --lang=go << EOF 95 | func test(v []string) int { 96 | return len(v) + 1; 97 | } 98 | EOF 99 | ``` 100 | 101 | The expected result is below. 102 | 103 | ``` 104 | [inline]: matched with the given rule 105 | In /dev/stdin: 106 | | 107 | 2 | return len(v) + 1; 108 | | 109 | ``` 110 | 111 | ### Pattern in Dockerfile 112 | 113 | Let's execute a below pattern `shisho find 'USER :[X]' --lang ...`. It searches whether the target `FROM node:10-alpine ...` includes the instruction `USER :[X]` with any values. 114 | 115 | ```shell 116 | $ shisho find 'USER :[X]' --lang=dockerfile << EOF 117 | FROM node:10-alpine 118 | RUN mkdir /app 119 | COPY . /app 120 | RUN chown -R node:node /app 121 | USER node 122 | CMD ["node", "index.js"] 123 | EOF 124 | ``` 125 | 126 | The expected result is below. 127 | 128 | ``` 129 | [inline]: matched with the given rule 130 | In /dev/stdin: 131 | | 132 | 5 | USER node 133 | | 134 | ``` 135 | 136 | -------------------------------------------------------------------------------- /src/core/matcher/literal.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{ 2 | matcher::{CaptureItem, UnverifiedMetavariable}, 3 | query::MetavariableId, 4 | }; 5 | use regex::Captures; 6 | 7 | pub fn match_string_pattern<'tree, 'query>( 8 | tvalue: &'tree str, 9 | qvalue: &'query str, 10 | ) -> Vec<Vec<UnverifiedMetavariable<'tree>>> { 11 | // TODO (enhancement): this should have better implementation :/ 12 | let qpattern = to_regex(qvalue); 13 | let metavariables = find_metavariables(qvalue); 14 | 15 | let qregex = regex::Regex::new(qpattern.as_str()).unwrap(); 16 | qregex 17 | .captures_iter(tvalue) 18 | .map(|rcaptures| { 19 | metavariables 20 | .iter() 21 | .filter_map(|mid| { 22 | rcaptures.name(mid).map(|x| { 23 | ( 24 | MetavariableId(mid.to_string()), 25 | CaptureItem::Literal(x.as_str().to_string()), 26 | ) 27 | }) 28 | }) 29 | .collect::<Vec<UnverifiedMetavariable>>() 30 | }) 31 | .collect() 32 | } 33 | 34 | fn find_metavariables(q: &str) -> Vec<&str> { 35 | let p = regex::Regex::new(r":\[(\.\.\.)?(?P<name>[A-Z_][A-Z_0-9]*)\]").unwrap(); 36 | p.captures_iter(q) 37 | .map(|x| x.name("name").unwrap().as_str()) 38 | .collect() 39 | } 40 | 41 | fn to_regex(q: &str) -> String { 42 | // TODO: handle backslash 43 | let escaped_qvalue = regex::escape(q); 44 | let p = regex::Regex::new(r":\\\[(\\.\\.\\.)?(?P<name>[A-Z_][A-Z_0-9]*)\\\]").unwrap(); 45 | format!( 46 | "(?s-m)\\A{}\\z", 47 | p.replace_all(escaped_qvalue.as_str(), |caps: &Captures| { 48 | let name = caps.name("name").unwrap().as_str(); 49 | if name == "_" { 50 | "(.*)".to_string() 51 | } else { 52 | format!("(?P<{}>.*)", name) 53 | } 54 | }) 55 | ) 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use super::*; 61 | 62 | #[test] 63 | fn test_to_regex() { 64 | assert_eq!(to_regex("test"), "(?s-m)\\Atest\\z"); 65 | 66 | assert_eq!(to_regex("te:[X]st"), "(?s-m)\\Ate(?P<X>.*)st\\z"); 67 | assert_eq!( 68 | to_regex("te:[X]s:[Y]t"), 69 | "(?s-m)\\Ate(?P<X>.*)s(?P<Y>.*)t\\z" 70 | ); 71 | 72 | assert_eq!(to_regex("te:[...X]st"), "(?s-m)\\Ate(?P<X>.*)st\\z"); 73 | assert_eq!( 74 | to_regex("te:[...X]s:[...Y]t"), 75 | "(?s-m)\\Ate(?P<X>.*)s(?P<Y>.*)t\\z" 76 | ); 77 | } 78 | 79 | #[test] 80 | fn test_find_metavariables() { 81 | assert_eq!(find_metavariables("test").len(), 0); 82 | 83 | assert_eq!(find_metavariables("te:[X]st"), vec!["X"]); 84 | assert_eq!(find_metavariables("te:[X]s:[Y]t"), vec!["X", "Y"]); 85 | 86 | assert_eq!(find_metavariables("te:[...X]st"), vec!["X"]); 87 | assert_eq!(find_metavariables("te:[...X]s:[...Y]t"), vec!["X", "Y"]); 88 | } 89 | 90 | #[test] 91 | fn test_match_string_pattern() { 92 | assert_eq!(match_string_pattern("test", "test").len(), 1); 93 | assert_eq!( 94 | match_string_pattern("hellotestgoodbye", "hello:[X]goodbye"), 95 | vec![vec![( 96 | MetavariableId("X".into()), 97 | CaptureItem::Literal("test".into()) 98 | )]] 99 | ); 100 | 101 | assert_eq!( 102 | match_string_pattern("hello\ntestgoodbye", "hello:[X]goodbye"), 103 | vec![vec![( 104 | MetavariableId("X".into()), 105 | CaptureItem::Literal("\ntest".into()) 106 | )]] 107 | ); 108 | 109 | // longest match 110 | assert_eq!( 111 | match_string_pattern("hellotestgoodbye", "hello:[X]:[Y]goodbye"), 112 | vec![vec![ 113 | ( 114 | MetavariableId("X".into()), 115 | CaptureItem::Literal("test".into()) 116 | ), 117 | (MetavariableId("Y".into()), CaptureItem::Literal("".into())) 118 | ]] 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /docs/src/components/NextPrevious.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './link'; 3 | 4 | import { StyledNextPrevious } from './styles/PageNavigationButtons'; 5 | 6 | const NextPrevious = ({ mdx, nav }) => { 7 | let currentIndex; 8 | 9 | const currentPaginationInfo = nav.map((el, index) => { 10 | if (el && el.url === mdx.fields.slug) { 11 | currentIndex = index; 12 | } 13 | }); 14 | 15 | const nextInfo = {}; 16 | 17 | const previousInfo = {}; 18 | 19 | if (currentIndex === undefined) { 20 | // index 21 | if (nav[0]) { 22 | nextInfo.url = nav[0].url; 23 | nextInfo.title = nav[0].title; 24 | } 25 | previousInfo.url = null; 26 | previousInfo.title = null; 27 | currentIndex = -1; 28 | } else if (currentIndex === 0) { 29 | // first page 30 | nextInfo.url = nav[currentIndex + 1] ? nav[currentIndex + 1].url : null; 31 | nextInfo.title = nav[currentIndex + 1] ? nav[currentIndex + 1].title : null; 32 | previousInfo.url = null; 33 | previousInfo.title = null; 34 | } else if (currentIndex === nav.length - 1) { 35 | // last page 36 | nextInfo.url = null; 37 | nextInfo.title = null; 38 | previousInfo.url = nav[currentIndex - 1] ? nav[currentIndex - 1].url : null; 39 | previousInfo.title = nav[currentIndex - 1] ? nav[currentIndex - 1].title : null; 40 | } else if (currentIndex) { 41 | // any other page 42 | nextInfo.url = nav[currentIndex + 1].url; 43 | nextInfo.title = nav[currentIndex + 1].title; 44 | if (nav[currentIndex - 1]) { 45 | previousInfo.url = nav[currentIndex - 1].url; 46 | previousInfo.title = nav[currentIndex - 1].title; 47 | } 48 | } 49 | 50 | return ( 51 | <StyledNextPrevious> 52 | {previousInfo.url && currentIndex >= 0 ? ( 53 | <Link to={nav[currentIndex - 1].url} className={'previousBtn'}> 54 | <div className={'leftArrow'}> 55 | <svg 56 | preserveAspectRatio="xMidYMid meet" 57 | height="1em" 58 | width="1em" 59 | fill="none" 60 | xmlns="http://www.w3.org/2000/svg" 61 | viewBox="0 0 24 24" 62 | strokeWidth="2" 63 | strokeLinecap="round" 64 | strokeLinejoin="round" 65 | stroke="currentColor" 66 | className="_13gjrqj" 67 | > 68 | <g> 69 | <line x1="19" y1="12" x2="5" y2="12" /> 70 | <polyline points="12 19 5 12 12 5" /> 71 | </g> 72 | </svg> 73 | </div> 74 | <div className={'preRightWrapper'}> 75 | <div className={'smallContent'}> 76 | <span>Previous</span> 77 | </div> 78 | <div className={'nextPreviousTitle'}> 79 | <span>{nav[currentIndex - 1].title}</span> 80 | </div> 81 | </div> 82 | </Link> 83 | ) : null} 84 | {nextInfo.url && currentIndex >= 0 ? ( 85 | <Link to={nav[currentIndex + 1].url} className={'nextBtn'}> 86 | <div className={'nextRightWrapper'}> 87 | <div className={'smallContent'}> 88 | <span>Next</span> 89 | </div> 90 | <div className={'nextPreviousTitle'}> 91 | <span>{nav[currentIndex + 1] && nav[currentIndex + 1].title}</span> 92 | </div> 93 | </div> 94 | <div className={'rightArrow'}> 95 | <svg 96 | preserveAspectRatio="xMidYMid meet" 97 | height="1em" 98 | width="1em" 99 | fill="none" 100 | xmlns="http://www.w3.org/2000/svg" 101 | viewBox="0 0 24 24" 102 | strokeWidth="2" 103 | strokeLinecap="round" 104 | strokeLinejoin="round" 105 | stroke="currentColor" 106 | className="_13gjrqj" 107 | > 108 | <g> 109 | <line x1="5" y1="12" x2="19" y2="12" /> 110 | <polyline points="12 5 19 12 12 19" /> 111 | </g> 112 | </svg> 113 | </div> 114 | </Link> 115 | ) : null} 116 | </StyledNextPrevious> 117 | ); 118 | }; 119 | 120 | export default NextPrevious; 121 | -------------------------------------------------------------------------------- /docs/gatsby-config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const queries = require("./src/utils/algolia"); 3 | const config = require("./config"); 4 | const plugins = [ 5 | 'gatsby-plugin-sitemap', 6 | 'gatsby-plugin-sharp', 7 | { 8 | resolve: `gatsby-plugin-layout`, 9 | options: { 10 | component: require.resolve(`./src/templates/docs.js`) 11 | } 12 | }, 13 | 'gatsby-plugin-emotion', 14 | 'gatsby-plugin-react-helmet', 15 | { 16 | resolve: "gatsby-source-filesystem", 17 | options: { 18 | name: "docs", 19 | path: `${__dirname}/content/` 20 | } 21 | }, 22 | { 23 | resolve: 'gatsby-plugin-mdx', 24 | options: { 25 | gatsbyRemarkPlugins: [ 26 | { 27 | resolve: "gatsby-remark-images", 28 | options: { 29 | maxWidth: 1035, 30 | sizeByPixelDensity: true 31 | } 32 | }, 33 | { 34 | resolve: 'gatsby-remark-copy-linked-files' 35 | } 36 | ], 37 | extensions: [".mdx", ".md"] 38 | } 39 | }, 40 | { 41 | resolve: `gatsby-plugin-gtag`, 42 | options: { 43 | // your google analytics tracking id 44 | trackingId: config.gatsby.gaTrackingId, 45 | // Puts tracking script in the head instead of the body 46 | head: true, 47 | // enable ip anonymization 48 | anonymize: false, 49 | }, 50 | }, 51 | ]; 52 | // check and add algolia 53 | if (config.header.search && config.header.search.enabled && config.header.search.algoliaAppId && config.header.search.algoliaAdminKey) { 54 | plugins.push({ 55 | resolve: `gatsby-plugin-algolia`, 56 | options: { 57 | appId: config.header.search.algoliaAppId, // algolia application id 58 | apiKey: config.header.search.algoliaAdminKey, // algolia admin key to index 59 | queries, 60 | chunkSize: 10000, // default: 1000 61 | }} 62 | ) 63 | } 64 | // check and add pwa functionality 65 | if (config.pwa && config.pwa.enabled && config.pwa.manifest) { 66 | plugins.push({ 67 | resolve: `gatsby-plugin-manifest`, 68 | options: {...config.pwa.manifest}, 69 | }); 70 | plugins.push({ 71 | resolve: 'gatsby-plugin-offline', 72 | options: { 73 | appendScript: require.resolve(`./src/custom-sw-code.js`), 74 | }, 75 | }); 76 | } else { 77 | plugins.push('gatsby-plugin-remove-serviceworker'); 78 | } 79 | 80 | // check and remove trailing slash 81 | if (config.gatsby && !config.gatsby.trailingSlash) { 82 | plugins.push('gatsby-plugin-remove-trailing-slashes'); 83 | } 84 | 85 | module.exports = { 86 | pathPrefix: config.gatsby.pathPrefix, 87 | siteMetadata: { 88 | title: config.siteMetadata.title, 89 | description: config.siteMetadata.description, 90 | docsLocation: config.siteMetadata.docsLocation, 91 | ogImage: config.siteMetadata.ogImage, 92 | favicon: config.siteMetadata.favicon, 93 | logo: { link: config.header.logoLink ? config.header.logoLink : '/', image: config.header.logo }, // backwards compatible 94 | headerTitle: config.header.title, 95 | githubUrl: config.header.githubUrl, 96 | helpUrl: config.header.helpUrl, 97 | tweetText: config.header.tweetText, 98 | headerLinks: config.header.links, 99 | siteUrl: config.gatsby.siteUrl, 100 | }, 101 | plugins: plugins, 102 | flags: { 103 | DEV_SSR: false, 104 | FAST_DEV: false, // Enable all experiments aimed at improving develop server start time 105 | PRESERVE_WEBPACK_CACHE: false, // (Umbrella Issue (https://gatsby.dev/cache-clearing-feedback)) · Use webpack's persistent caching and don't delete webpack's cache when changing gatsby-node.js & gatsby-config.js files. 106 | PRESERVE_FILE_DOWNLOAD_CACHE: false, // (Umbrella Issue (https://gatsby.dev/cache-clearing-feedback)) · Don't delete the downloaded files cache when changing gatsby-node.js & gatsby-config.js files. 107 | PARALLEL_SOURCING: false, // EXPERIMENTAL · (Umbrella Issue (https://gatsby.dev/parallel-sourcing-feedback)) · Run all source plugins at the same time instead of serially. For sites with multiple source plugins, this can speedup sourcing and transforming considerably. 108 | FUNCTIONS: false // EXPERIMENTAL · (Umbrella Issue (https://gatsby.dev/functions-feedback)) · Compile Serverless functions in your Gatsby project and write them to disk, ready to deploy to Gatsby Cloud 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /docs/src/components/sidebar/tree.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import config from '../../../config'; 3 | import TreeNode from './treeNode'; 4 | 5 | const calculateTreeData = edges => { 6 | const originalData = config.sidebar.ignoreIndex 7 | ? edges.filter( 8 | ({ 9 | node: { 10 | fields: { slug }, 11 | }, 12 | }) => slug !== '/' 13 | ) 14 | : edges; 15 | 16 | const tree = originalData.reduce( 17 | ( 18 | accu, 19 | { 20 | node: { 21 | fields: { slug, title }, 22 | }, 23 | } 24 | ) => { 25 | const parts = slug.split('/'); 26 | 27 | let { items: prevItems } = accu; 28 | 29 | const slicedParts = 30 | config.gatsby && config.gatsby.trailingSlash ? parts.slice(1, -2) : parts.slice(1, -1); 31 | 32 | for (const part of slicedParts) { 33 | let tmp = prevItems && prevItems.find(({ label }) => label == part); 34 | 35 | if (tmp) { 36 | if (!tmp.items) { 37 | tmp.items = []; 38 | } 39 | } else { 40 | tmp = { label: part, items: [] }; 41 | prevItems.push(tmp); 42 | } 43 | prevItems = tmp.items; 44 | } 45 | const slicedLength = 46 | config.gatsby && config.gatsby.trailingSlash ? parts.length - 2 : parts.length - 1; 47 | 48 | const existingItem = prevItems.find(({ label }) => label === parts[slicedLength]); 49 | 50 | if (existingItem) { 51 | existingItem.url = slug; 52 | existingItem.title = title; 53 | } else { 54 | prevItems.push({ 55 | label: parts[slicedLength], 56 | url: slug, 57 | items: [], 58 | title, 59 | }); 60 | } 61 | return accu; 62 | }, 63 | { items: [] } 64 | ); 65 | 66 | const { 67 | sidebar: { forcedNavOrder = [] }, 68 | } = config; 69 | 70 | const tmp = [...forcedNavOrder]; 71 | 72 | if (config.gatsby && config.gatsby.trailingSlash) { 73 | } 74 | tmp.reverse(); 75 | return tmp.reduce((accu, slug) => { 76 | const parts = slug.split('/'); 77 | 78 | let { items: prevItems } = accu; 79 | 80 | const slicedParts = 81 | config.gatsby && config.gatsby.trailingSlash ? parts.slice(1, -2) : parts.slice(1, -1); 82 | 83 | for (const part of slicedParts) { 84 | let tmp = prevItems.find(item => item && item.label == part); 85 | 86 | if (tmp) { 87 | if (!tmp.items) { 88 | tmp.items = []; 89 | } 90 | } else { 91 | tmp = { label: part, items: [] }; 92 | prevItems.push(tmp); 93 | } 94 | if (tmp && tmp.items) { 95 | prevItems = tmp.items; 96 | } 97 | } 98 | // sort items alphabetically. 99 | prevItems.map(item => { 100 | item.items = item.items.sort(function(a, b) { 101 | if (a.label < b.label) return -1; 102 | if (a.label > b.label) return 1; 103 | return 0; 104 | }); 105 | }); 106 | const slicedLength = 107 | config.gatsby && config.gatsby.trailingSlash ? parts.length - 2 : parts.length - 1; 108 | 109 | const index = prevItems.findIndex(({ label }) => label === parts[slicedLength]); 110 | 111 | if (prevItems.length) { 112 | accu.items.unshift(prevItems.splice(index, 1)[0]); 113 | } 114 | return accu; 115 | }, tree); 116 | }; 117 | 118 | const Tree = ({ edges }) => { 119 | let [treeData] = useState(() => { 120 | return calculateTreeData(edges); 121 | }); 122 | 123 | const defaultCollapsed = {}; 124 | 125 | treeData.items.forEach(item => { 126 | if (config.sidebar.collapsedNav && config.sidebar.collapsedNav.includes(item.url)) { 127 | defaultCollapsed[item.url] = true; 128 | } else { 129 | defaultCollapsed[item.url] = false; 130 | } 131 | }); 132 | const [collapsed, setCollapsed] = useState(defaultCollapsed); 133 | 134 | const toggle = url => { 135 | setCollapsed({ 136 | ...collapsed, 137 | [url]: !collapsed[url], 138 | }); 139 | }; 140 | 141 | return ( 142 | <TreeNode 143 | className={`${config.sidebar.frontLine ? 'showFrontLine' : 'hideFrontLine'} firstLevel`} 144 | setCollapsed={toggle} 145 | collapsed={collapsed} 146 | {...treeData} 147 | /> 148 | ); 149 | }; 150 | 151 | export default Tree; 152 | -------------------------------------------------------------------------------- /docs/src/components/search/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, createRef } from 'react'; 2 | import { 3 | InstantSearch, 4 | Index, 5 | Hits, 6 | Configure, 7 | Pagination, 8 | connectStateResults, 9 | } from 'react-instantsearch-dom'; 10 | import algoliasearch from 'algoliasearch/lite'; 11 | import config from '../../../config.js'; 12 | 13 | import styled from '@emotion/styled'; 14 | import { css } from '@emotion/react'; 15 | import { PoweredBy } from './styles'; 16 | import { Search } from '@styled-icons/fa-solid/Search'; 17 | import Input from './input'; 18 | import * as hitComps from './hitComps'; 19 | 20 | const SearchIcon = styled(Search)` 21 | width: 1em; 22 | pointer-events: none; 23 | `; 24 | 25 | const HitsWrapper = styled.div` 26 | display: ${props => (props.show ? `grid` : `none`)}; 27 | max-height: 80vh; 28 | overflow: scroll; 29 | z-index: 2; 30 | -webkit-overflow-scrolling: touch; 31 | position: absolute; 32 | right: 0; 33 | top: calc(100% + 0.5em); 34 | width: 80vw; 35 | max-width: 30em; 36 | box-shadow: 0 0 5px 0; 37 | padding: 0.7em 1em 0.4em; 38 | background: white; 39 | @media only screen and (max-width: 991px) { 40 | width: 400px; 41 | max-width: 400px; 42 | } 43 | @media only screen and (max-width: 767px) { 44 | width: 100%; 45 | max-width: 500px; 46 | } 47 | border-radius: ${props => props.theme.smallBorderRadius}; 48 | > * + * { 49 | padding-top: 1em !important; 50 | border-top: 2px solid ${props => props.theme.darkGray}; 51 | } 52 | li + li { 53 | margin-top: 0.7em; 54 | padding-top: 0.7em; 55 | border-top: 1px solid ${props => props.theme.lightGray}; 56 | } 57 | * { 58 | margin-top: 0; 59 | padding: 0; 60 | color: black !important; 61 | } 62 | ul { 63 | list-style: none; 64 | } 65 | mark { 66 | color: ${props => props.theme.lightBlue}; 67 | background: ${props => props.theme.darkBlue}; 68 | } 69 | header { 70 | display: flex; 71 | justify-content: space-between; 72 | margin-bottom: 0.3em; 73 | h3 { 74 | color: black; 75 | background: ${props => props.theme.gray}; 76 | padding: 0.1em 0.4em; 77 | border-radius: ${props => props.theme.smallBorderRadius}; 78 | } 79 | } 80 | h3 { 81 | color: black; 82 | margin: 0 0 0.5em; 83 | } 84 | h4 { 85 | color: black; 86 | margin-bottom: 0.3em; 87 | } 88 | `; 89 | 90 | const Root = styled.div` 91 | position: relative; 92 | display: grid; 93 | grid-gap: 1em; 94 | @media only screen and (max-width: 767px) { 95 | width: 100%; 96 | } 97 | `; 98 | 99 | const Results = connectStateResults( 100 | ({ searching, searchState: state, searchResults: res }) => 101 | (searching && `Searching...`) || (res && res.nbHits === 0 && `No results for '${state.query}'`) 102 | ); 103 | 104 | const useClickOutside = (ref, handler, events) => { 105 | if (!events) events = [`mousedown`, `touchstart`]; 106 | const detectClickOutside = event => 107 | ref && ref.current && !ref.current.contains(event.target) && handler(); 108 | 109 | useEffect(() => { 110 | for (const event of events) document.addEventListener(event, detectClickOutside); 111 | return () => { 112 | for (const event of events) document.removeEventListener(event, detectClickOutside); 113 | }; 114 | }); 115 | }; 116 | 117 | const searchClient = algoliasearch( 118 | config.header.search.algoliaAppId, 119 | config.header.search.algoliaSearchKey 120 | ); 121 | 122 | export default function SearchComponent({ indices, collapse, hitsAsGrid }) { 123 | const ref = createRef(); 124 | 125 | const [query, setQuery] = useState(``); 126 | 127 | const [focus, setFocus] = useState(false); 128 | 129 | useClickOutside(ref, () => setFocus(false)); 130 | const displayResult = query.length > 0 && focus ? 'showResults' : 'hideResults'; 131 | return ( 132 | <InstantSearch 133 | searchClient={searchClient} 134 | indexName={indices[0].name} 135 | onSearchStateChange={({ query }) => setQuery(query)} 136 | root={{ Root, props: { ref } }} 137 | > 138 | <Input onFocus={() => setFocus(true)} {...{ collapse, focus }} /> 139 | <HitsWrapper 140 | className={'hitWrapper ' + displayResult} 141 | show={query.length > 0 && focus} 142 | asGrid={hitsAsGrid} 143 | > 144 | {indices.map(({ name, title, hitComp, type }) => { 145 | return ( 146 | <Index key={name} indexName={name}> 147 | <Results /> 148 | <Hits hitComponent={hitComps[hitComp](() => setFocus(false))} /> 149 | </Index> 150 | ); 151 | })} 152 | <PoweredBy /> 153 | </HitsWrapper> 154 | <Configure hitsPerPage={5} /> 155 | </InstantSearch> 156 | ); 157 | } 158 | -------------------------------------------------------------------------------- /docs/src/templates/docs.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Helmet from 'react-helmet'; 3 | import { graphql } from 'gatsby'; 4 | import MDXRenderer from 'gatsby-plugin-mdx/mdx-renderer'; 5 | 6 | import { Layout, Link } from '$components'; 7 | import NextPrevious from '../components/NextPrevious'; 8 | import config from '../../config'; 9 | import { Edit, StyledHeading, StyledMainWrapper } from '../components/styles/Docs'; 10 | 11 | const forcedNavOrder = config.sidebar.forcedNavOrder; 12 | 13 | export default class MDXRuntimeTest extends Component { 14 | render() { 15 | const { data } = this.props; 16 | 17 | if (!data) { 18 | return this.props.children; 19 | } 20 | const { 21 | allMdx, 22 | mdx, 23 | site: { 24 | siteMetadata: { docsLocation, title }, 25 | }, 26 | } = data; 27 | 28 | const githubIcon = require('../components/images/github.svg').default; 29 | const navItems = allMdx.edges 30 | .map(({ node }) => node.fields.slug) 31 | .filter(slug => slug !== '/') 32 | .sort() 33 | .reduce( 34 | (acc, cur) => { 35 | if (forcedNavOrder.find(url => url === cur)) { 36 | return { ...acc, [cur]: [cur] }; 37 | } 38 | 39 | let prefix = cur.split('/')[1]; 40 | 41 | if (config.gatsby && config.gatsby.trailingSlash) { 42 | prefix = prefix + '/'; 43 | } 44 | 45 | if (prefix && forcedNavOrder.find(url => url === `/${prefix}`)) { 46 | return { ...acc, [`/${prefix}`]: [...acc[`/${prefix}`], cur] }; 47 | } else { 48 | return { ...acc, items: [...acc.items, cur] }; 49 | } 50 | }, 51 | { items: [] } 52 | ); 53 | 54 | const nav = forcedNavOrder 55 | .reduce((acc, cur) => { 56 | return acc.concat(navItems[cur]); 57 | }, []) 58 | .concat(navItems.items) 59 | .map(slug => { 60 | if (slug) { 61 | const { node } = allMdx.edges.find(({ node }) => node.fields.slug === slug); 62 | 63 | return { title: node.fields.title, url: node.fields.slug }; 64 | } 65 | }); 66 | 67 | // meta tags 68 | const metaTitle = mdx.frontmatter.metaTitle; 69 | 70 | const metaDescription = mdx.frontmatter.metaDescription; 71 | 72 | let canonicalUrl = config.gatsby.siteUrl; 73 | 74 | canonicalUrl = 75 | config.gatsby.pathPrefix !== '/' ? canonicalUrl + config.gatsby.pathPrefix : canonicalUrl; 76 | canonicalUrl = canonicalUrl + mdx.fields.slug; 77 | 78 | return ( 79 | <Layout {...this.props}> 80 | <Helmet> 81 | {metaTitle ? <title>{metaTitle} | {title} : null} 82 | {metaTitle ? : null} 83 | {metaDescription ? : null} 84 | {metaTitle ? : null} 85 | {metaDescription ? : null} 86 | {metaTitle ? : null} 87 | {metaDescription ? ( 88 | 89 | ) : null} 90 | 91 | 92 | {mdx.fields.slug !== "/" &&
93 | {mdx.fields.title} 94 | 95 | {docsLocation && ( 96 | 97 | {'Github Edit on GitHub 98 | 99 | )} 100 | 101 |
} 102 | 103 | {mdx.body} 104 | 105 |
106 | 107 |
108 | 109 | ); 110 | } 111 | } 112 | 113 | export const pageQuery = graphql` 114 | query($id: String!) { 115 | site { 116 | siteMetadata { 117 | title 118 | docsLocation 119 | } 120 | } 121 | mdx(fields: { id: { eq: $id } }) { 122 | fields { 123 | id 124 | title 125 | slug 126 | } 127 | body 128 | tableOfContents 129 | parent { 130 | ... on File { 131 | relativePath 132 | } 133 | } 134 | frontmatter { 135 | metaTitle 136 | metaDescription 137 | } 138 | } 139 | allMdx { 140 | edges { 141 | node { 142 | fields { 143 | slug 144 | title 145 | } 146 | } 147 | } 148 | } 149 | } 150 | `; 151 | -------------------------------------------------------------------------------- /docs/src/components/mdxComponents/codeBlock.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Highlight, { defaultProps, Prism } from 'prism-react-renderer'; 3 | import { applyLanguages, getTheme } from '../../custom/config/codeBlockLanguages'; 4 | import Loadable from 'react-loadable'; 5 | import LoadingProvider from './loading'; 6 | 7 | const theme = getTheme(); 8 | 9 | /** Removes the last token from a code example if it's empty. */ 10 | function cleanTokens(tokens) { 11 | const tokensLength = tokens.length; 12 | 13 | if (tokensLength === 0) { 14 | return tokens; 15 | } 16 | const lastToken = tokens[tokensLength - 1]; 17 | 18 | if (lastToken.length === 1 && lastToken[0].empty) { 19 | return tokens.slice(0, tokensLength - 1); 20 | } 21 | return tokens; 22 | } 23 | 24 | const LoadableComponent = Loadable({ 25 | loader: () => import('./LiveProvider'), 26 | loading: LoadingProvider, 27 | }); 28 | 29 | /* eslint-disable react/jsx-key */ 30 | const CodeBlock = ({ children: exampleCode, ...props }) => { 31 | const [_, updateView] = React.useState(0); 32 | 33 | React.useEffect(() => { 34 | var windowPrism = window.Prism; 35 | window.Prism = Prism; 36 | applyLanguages(Prism); 37 | window.Prism = windowPrism; 38 | updateView({ 39 | data: Date.now() 40 | }); 41 | }, []); 42 | 43 | if (props['react-live']) { 44 | return ; 45 | } else { 46 | return ( 47 | 48 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 49 |
 50 |             {cleanTokens(tokens).map((line, i) => {
 51 |               let lineClass = {};
 52 | 
 53 |               let isDiff = false;
 54 | 
 55 |               if (line[0] && line[0].content.length && line[0].content[0] === '+') {
 56 |                 lineClass = { backgroundColor: 'rgba(76, 175, 80, 0.2)' };
 57 |                 isDiff = true;
 58 |               } else if (line[0] && line[0].content.length && line[0].content[0] === '-') {
 59 |                 lineClass = { backgroundColor: 'rgba(244, 67, 54, 0.2)' };
 60 |                 isDiff = true;
 61 |               } else if (line[0] && line[0].content === '' && line[1] && line[1].content === '+') {
 62 |                 lineClass = { backgroundColor: 'rgba(76, 175, 80, 0.2)' };
 63 |                 isDiff = true;
 64 |               } else if (line[0] && line[0].content === '' && line[1] && line[1].content === '-') {
 65 |                 lineClass = { backgroundColor: 'rgba(244, 67, 54, 0.2)' };
 66 |                 isDiff = true;
 67 |               }
 68 |               const lineProps = getLineProps({ line, key: i });
 69 | 
 70 |               lineProps.style = lineClass;
 71 |               const diffStyle = {
 72 |                 userSelect: 'none',
 73 |                 MozUserSelect: '-moz-none',
 74 |                 WebkitUserSelect: 'none',
 75 |               };
 76 | 
 77 |               let splitToken;
 78 | 
 79 |               return (
 80 |                 
81 | {line.map((token, key) => { 82 | if (isDiff) { 83 | if ( 84 | (key === 0 || key === 1) & 85 | (token.content.charAt(0) === '+' || token.content.charAt(0) === '-') 86 | ) { 87 | if (token.content.length > 1) { 88 | splitToken = { 89 | types: ['template-string', 'string'], 90 | content: token.content.slice(1), 91 | }; 92 | const firstChar = { 93 | types: ['operator'], 94 | content: token.content.charAt(0), 95 | }; 96 | 97 | return ( 98 | 99 | 103 | 104 | 105 | ); 106 | } else { 107 | return ; 108 | } 109 | } 110 | } 111 | return ; 112 | })} 113 |
114 | ); 115 | })} 116 |
117 | )} 118 |
119 | ); 120 | } 121 | }; 122 | 123 | export default CodeBlock; 124 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | paths-ignore: 8 | - "docs" 9 | - ".vscode" 10 | 11 | jobs: 12 | build: 13 | name: Build all 14 | strategy: 15 | matrix: 16 | target: 17 | - x86_64-unknown-linux-gnu 18 | - x86_64-pc-windows-gnu 19 | - x86_64-apple-darwin 20 | include: 21 | - target: x86_64-unknown-linux-gnu 22 | os: ubuntu-latest 23 | - target: x86_64-pc-windows-gnu 24 | os: ubuntu-latest 25 | - target: x86_64-apple-darwin 26 | os: macos-latest 27 | 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - name: Checkout HEAD 31 | uses: actions/checkout@v1 32 | with: 33 | submodules: true 34 | - name: Setup Rust 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | profile: minimal 38 | toolchain: stable 39 | override: true 40 | 41 | - name: Cache cargo registry 42 | uses: actions/cache@v1 43 | with: 44 | path: ~/.cargo/registry 45 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 46 | - name: Cache cargo index 47 | uses: actions/cache@v1 48 | with: 49 | path: ~/.cargo/git 50 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} 51 | - name: Cache cargo build 52 | uses: actions/cache@v1 53 | with: 54 | path: target 55 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 56 | 57 | - uses: actions-rs/cargo@v1.0.1 58 | with: 59 | command: build 60 | args: --release --target=${{ matrix.target }} 61 | use-cross: true 62 | - name: Compress release files 63 | run: | 64 | zip --junk-paths shisho-${{ matrix.target }} target/${{ matrix.target }}/release/shisho{,.exe} 65 | - uses: actions/upload-artifact@v1 66 | with: 67 | name: build-${{ matrix.target }} 68 | path: shisho-${{ matrix.target }}.zip 69 | 70 | create-release: 71 | name: Create Github Release 72 | needs: 73 | - build 74 | runs-on: ubuntu-latest 75 | steps: 76 | - name: Create a GitHub release 77 | id: create-release 78 | uses: actions/create-release@v1 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | with: 82 | tag_name: ${{ github.ref }} 83 | release_name: Release ${{ github.ref }} 84 | draft: false 85 | - run: | 86 | echo '${{ steps.create-release.outputs.upload_url }}' > release_upload_url.txt 87 | - uses: actions/upload-artifact@v1 88 | with: 89 | name: create-release 90 | path: release_upload_url.txt 91 | 92 | upload-assets: 93 | name: Create Github Release 94 | needs: 95 | - create-release 96 | strategy: 97 | matrix: 98 | target: 99 | - x86_64-unknown-linux-gnu 100 | - x86_64-pc-windows-gnu 101 | - x86_64-apple-darwin 102 | runs-on: ubuntu-latest 103 | steps: 104 | - name: Fetch meta artifacts 105 | uses: actions/download-artifact@v1 106 | with: 107 | name: create-release 108 | - name: Extract an upload URL 109 | id: upload-url 110 | run: | 111 | echo "::set-output name=url::$(cat create-release/release_upload_url.txt)" 112 | - name: Download actual artifacts 113 | uses: actions/download-artifact@v1 114 | with: 115 | name: build-${{ matrix.target }} 116 | - uses: actions/upload-release-asset@v1 117 | env: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | with: 120 | upload_url: ${{ steps.upload-url.outputs.url }} 121 | asset_name: build-${{ matrix.target }}.zip 122 | asset_path: build-${{ matrix.target }}/shisho-${{ matrix.target }}.zip 123 | asset_content_type: application/zip 124 | 125 | release-image: 126 | runs-on: ubuntu-latest 127 | env: 128 | IMAGE_NAME: shisho-cli 129 | steps: 130 | - name: Checkout HEAD 131 | uses: actions/checkout@v1 132 | with: 133 | submodules: true 134 | 135 | - name: Set up Docker Buildx 136 | uses: docker/setup-buildx-action@v1 137 | 138 | - name: Login to GitHub Container Registry 139 | uses: docker/login-action@v1 140 | with: 141 | registry: ghcr.io 142 | username: ${{ github.repository_owner }} 143 | password: ${{ secrets.GITHUB_TOKEN }} 144 | 145 | - name: Retrive version string 146 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 147 | 148 | - name: Build and push 149 | uses: docker/build-push-action@v2 150 | with: 151 | context: . 152 | push: true 153 | tags: | 154 | ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest 155 | ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.RELEASE_VERSION }} 156 | -------------------------------------------------------------------------------- /docs/content/shisho-cloud/frequently-asked-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Frequently Asked Questions' 3 | metaTitle: 'Frequently Asked Questions - Shisho Cloud' 4 | metaDescription: 'This page shows frequently asked questions.' 5 | --- 6 | 7 | ### Sign Up 8 | 9 | #### I do not have any SSO accounts. What should I do? 10 | 11 | At this stage, we do not provide email/password-based sign-up and the other SSO options. For the registration, [Shisho Cloud](https://shisho.dev/) needs to integrate with at least one of the repository managers. This way gives you centrally to manage both the account and integration configuration. If we continuously receive the request, we consider them but please consider creating the account of the repository managers. 12 | 13 | #### Can I sign up with multiple SSOs? 14 | 15 | Yes, but [Shisho Cloud](https://shisho.dev/) treats your multiple accounts as different accounts even though you work with the same repositories in repository managers. 16 | 17 | ### Repository connection 18 | 19 | #### Can I link with repositories of multiple repository managers such as GitHub and Bitbucket? 20 | 21 | Yes, you are able to link with repositories of multiple repository managers. We know some enterprises and small businesses separately own repositories due to the company policy. You do not need to sign up with each of them, please simply log in by the current SSO and connect with the other repository managers. 22 | 23 | #### Can I connect Shisho Cloud with my own Git server? 24 | 25 | Unfortunately, we do not support the own Git servers at this stage but we of course consider that. If you seriously want, please send feedback. We might prioritize it higher. 26 | 27 | #### Can I connect Shisho Cloud with other services such as Azure Repos? 28 | 29 | We support [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/), and [BitBucket](https://bitbucket.org/product) at the moment but we consider expanding the other services. If you want, please send feedback regarding the request. We might change task priorities. 30 | 31 | #### I want to test Shisho Cloud but I have not had Terraform code yet. Is it possible? 32 | 33 | Please consider trying a test repository. You might be able to understand what IaC is and why the security is significant for it. As you know, IaC such as [Terrafrom](https://www.terraform.io/) is useful and powerful to create, update and destroy cloud resources. We are happy to support the new journey of secured cloud resource management. 34 | 35 | ### Test repository "[flatt-security/tfgoat-aws](https://github.com/flatt-security/tfgoat-aws)" 36 | 37 | #### What is a test repository? 38 | 39 | The test repository, "[flatt-security/tfgoat-aws](https://github.com/flatt-security/tfgoat-aws)" is our vulnerable-by-design [Terrafrom](https://www.terraform.io/) repository for testing purposes. Why we created is that some clients want us to demonstrate [Shisho Cloud](https://shisho.dev/) without connecting with their repositories. We are pretty sure that it is enough to assess the quality and performance of [Shisho Cloud](https://shisho.dev/). 40 | 41 | #### So, will I have any risks with the test repository? 42 | 43 | No worries, friends. You should not have any troubles with your cloud services and of course, repositories as well. The [Terrafrom](https://www.terraform.io/) code misconfigures [AWS](https://aws.amazon.com/) resources and policies for dummy resources on purpose but it will never pose critical incidents for your existing resources. 44 | 45 | #### Is it safe if I keep the test repository? 46 | 47 | Of course, yes. You should not have any troubles with your cloud services and repositories. However, if you want to delete it, you can remove it from your repository managers, [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/), and [BitBucket](https://bitbucket.org/product). Moreover, you can delete the repository integration OR your account itself of [Shisho Cloud](https://shisho.dev/). 48 | 49 | ### Shisho GitHub App 50 | 51 | #### What is Shisho GitHub App? 52 | 53 | Shisho GitHub App is one of the official "GitHub Apps" and our [GitHub](https://github.com/) repository integration requires it. Shisho GitHub Apps can be installed directly on organizations and user accounts and granted access to specific repositories via GitHub Apps. 54 | 55 | #### I do not want to install Shisho GitHub App on my PC. Is it OK? 56 | 57 | Some people are confused "GitHub Apps" as a desktop application that you need to install for your machines. This is an extension for your [GitHub](https://github.com/) account for the seamless integration and your manageable identities and assets. 58 | 59 | #### I cannot install Shisho GitHub App for repositories managed by my employer. Why? 60 | 61 | If you want to use [Shisho Cloud](https://shisho.dev/) with your workplace repositories, you might need to ask your repository administrator to install the app at the organization level. If it is difficult for testing purposes, please consider installing it on your private account. 62 | 63 | #### Is it possible to uninstall Shisho GitHub App? 64 | 65 | Yes, you can uninstall Shisho GitHub App from [GitHub](https://github.com/) Profile menu. Please go to Settings -> Applications / Integrations section. 66 | 67 | ### Shisho Cloud account 68 | 69 | #### Is it possible to delete Shisho Cloud? 70 | 71 | Yes, you can delete the [Shisho Cloud](https://shisho.dev/) account by yourself but we might cry :( We remove SSO details for login and discard all your work. -------------------------------------------------------------------------------- /src/cli/encoding.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use encoding_rs::Encoding; 3 | 4 | pub fn parse_encoding(label: &str) -> Result<&'static Encoding> { 5 | match Encoding::for_label((label).as_bytes()) { 6 | Some(encoding) => Ok(encoding), 7 | None => Err(anyhow::anyhow!("failed to parse encoding {}", label)), 8 | } 9 | } 10 | 11 | // This list comes from encoding_rs, which is licensed under Apache 2.0 or MIT. 12 | // https://github.com/hsivonen/encoding_rs/blob/19efaf064f5b95477cc1e0b487566d213a6d67f3/src/lib.rs#L2140 13 | pub static LABELS_SORTED: [&str; 219] = [ 14 | "l1", 15 | "l2", 16 | "l3", 17 | "l4", 18 | "l5", 19 | "l6", 20 | "l9", 21 | "866", 22 | "mac", 23 | "koi", 24 | "gbk", 25 | "big5", 26 | "utf8", 27 | "koi8", 28 | "sjis", 29 | "ms932", 30 | "cp866", 31 | "utf-8", 32 | "cp819", 33 | "ascii", 34 | "x-gbk", 35 | "greek", 36 | "cp1250", 37 | "cp1251", 38 | "latin1", 39 | "gb2312", 40 | "cp1252", 41 | "latin2", 42 | "cp1253", 43 | "latin3", 44 | "cp1254", 45 | "latin4", 46 | "cp1255", 47 | "csbig5", 48 | "latin5", 49 | "utf-16", 50 | "cp1256", 51 | "ibm866", 52 | "latin6", 53 | "cp1257", 54 | "cp1258", 55 | "greek8", 56 | "ibm819", 57 | "arabic", 58 | "visual", 59 | "korean", 60 | "euc-jp", 61 | "koi8-r", 62 | "koi8_r", 63 | "euc-kr", 64 | "x-sjis", 65 | "koi8-u", 66 | "hebrew", 67 | "tis-620", 68 | "gb18030", 69 | "ksc5601", 70 | "gb_2312", 71 | "dos-874", 72 | "cn-big5", 73 | "chinese", 74 | "logical", 75 | "cskoi8r", 76 | "cseuckr", 77 | "koi8-ru", 78 | "x-cp1250", 79 | "ksc_5601", 80 | "x-cp1251", 81 | "iso88591", 82 | "csgb2312", 83 | "x-cp1252", 84 | "iso88592", 85 | "x-cp1253", 86 | "iso88593", 87 | "ecma-114", 88 | "x-cp1254", 89 | "iso88594", 90 | "x-cp1255", 91 | "iso88595", 92 | "x-x-big5", 93 | "x-cp1256", 94 | "csibm866", 95 | "iso88596", 96 | "x-cp1257", 97 | "iso88597", 98 | "asmo-708", 99 | "ecma-118", 100 | "elot_928", 101 | "x-cp1258", 102 | "iso88598", 103 | "iso88599", 104 | "cyrillic", 105 | "utf-16be", 106 | "utf-16le", 107 | "us-ascii", 108 | "ms_kanji", 109 | "x-euc-jp", 110 | "iso885910", 111 | "iso8859-1", 112 | "iso885911", 113 | "iso8859-2", 114 | "iso8859-3", 115 | "iso885913", 116 | "iso8859-4", 117 | "iso885914", 118 | "iso8859-5", 119 | "iso885915", 120 | "iso8859-6", 121 | "iso8859-7", 122 | "iso8859-8", 123 | "iso-ir-58", 124 | "iso8859-9", 125 | "macintosh", 126 | "shift-jis", 127 | "shift_jis", 128 | "iso-ir-100", 129 | "iso8859-10", 130 | "iso-ir-110", 131 | "gb_2312-80", 132 | "iso-8859-1", 133 | "iso_8859-1", 134 | "iso-ir-101", 135 | "iso8859-11", 136 | "iso-8859-2", 137 | "iso_8859-2", 138 | "hz-gb-2312", 139 | "iso-8859-3", 140 | "iso_8859-3", 141 | "iso8859-13", 142 | "iso-8859-4", 143 | "iso_8859-4", 144 | "iso8859-14", 145 | "iso-ir-144", 146 | "iso-8859-5", 147 | "iso_8859-5", 148 | "iso8859-15", 149 | "iso-8859-6", 150 | "iso_8859-6", 151 | "iso-ir-126", 152 | "iso-8859-7", 153 | "iso_8859-7", 154 | "iso-ir-127", 155 | "iso-ir-157", 156 | "iso-8859-8", 157 | "iso_8859-8", 158 | "iso-ir-138", 159 | "iso-ir-148", 160 | "iso-8859-9", 161 | "iso_8859-9", 162 | "iso-ir-109", 163 | "iso-ir-149", 164 | "big5-hkscs", 165 | "csshiftjis", 166 | "iso-8859-10", 167 | "iso-8859-11", 168 | "csisolatin1", 169 | "csisolatin2", 170 | "iso-8859-13", 171 | "csisolatin3", 172 | "iso-8859-14", 173 | "windows-874", 174 | "csisolatin4", 175 | "iso-8859-15", 176 | "iso_8859-15", 177 | "csisolatin5", 178 | "iso-8859-16", 179 | "csisolatin6", 180 | "windows-949", 181 | "csisolatin9", 182 | "csiso88596e", 183 | "csiso88598e", 184 | "csmacintosh", 185 | "csiso88596i", 186 | "csiso88598i", 187 | "windows-31j", 188 | "x-mac-roman", 189 | "iso-2022-cn", 190 | "iso-2022-jp", 191 | "csiso2022jp", 192 | "iso-2022-kr", 193 | "csiso2022kr", 194 | "replacement", 195 | "windows-1250", 196 | "windows-1251", 197 | "windows-1252", 198 | "windows-1253", 199 | "windows-1254", 200 | "windows-1255", 201 | "windows-1256", 202 | "windows-1257", 203 | "windows-1258", 204 | "iso-8859-6-e", 205 | "iso-8859-8-e", 206 | "iso-8859-6-i", 207 | "iso-8859-8-i", 208 | "sun_eu_greek", 209 | "csksc56011987", 210 | "ks_c_5601-1987", 211 | "ansi_x3.4-1968", 212 | "ks_c_5601-1989", 213 | "x-mac-cyrillic", 214 | "x-user-defined", 215 | "csiso58gb231280", 216 | "iso_8859-1:1987", 217 | "iso_8859-2:1987", 218 | "iso_8859-6:1987", 219 | "iso_8859-7:1987", 220 | "iso_8859-3:1988", 221 | "iso_8859-4:1988", 222 | "iso_8859-5:1988", 223 | "iso_8859-8:1988", 224 | "iso_8859-9:1989", 225 | "csisolatingreek", 226 | "x-mac-ukrainian", 227 | "iso-2022-cn-ext", 228 | "csisolatinarabic", 229 | "csisolatinhebrew", 230 | "unicode-1-1-utf-8", 231 | "csisolatincyrillic", 232 | "cseucpkdfmtjapanese", 233 | ]; 234 | -------------------------------------------------------------------------------- /src/cli/reporter/sarif.rs: -------------------------------------------------------------------------------- 1 | use super::Reporter; 2 | use crate::core::{ 3 | language::Queryable, 4 | matcher::MatchedItem, 5 | ruleset::{Rule, Severity}, 6 | target::Target, 7 | }; 8 | use anyhow::Result; 9 | use serde_sarif::sarif; 10 | use std::{collections::HashMap, convert::TryInto}; 11 | 12 | pub struct SARIFReporter<'a, Writer: std::io::Write> { 13 | writer: &'a mut Writer, 14 | 15 | results: Vec, 16 | 17 | descriptors_idx_map: HashMap, 18 | descriptors: Vec, 19 | } 20 | 21 | impl<'a, W: std::io::Write> Reporter<'a> for SARIFReporter<'a, W> { 22 | type Writer = W; 23 | fn new(writer: &'a mut Self::Writer) -> Self { 24 | Self { 25 | writer, 26 | 27 | results: vec![], 28 | 29 | descriptors_idx_map: HashMap::new(), 30 | descriptors: vec![], 31 | } 32 | } 33 | 34 | fn add_entry( 35 | &mut self, 36 | target: &Target, 37 | items: Vec<(&Rule, MatchedItem)>, 38 | ) -> Result<()> { 39 | for (rule, mitem) in items { 40 | let descriptor_idx = { 41 | if let Some(idx) = self.descriptors_idx_map.get(&rule.id) { 42 | *idx 43 | } else { 44 | let descriptor = sarif::ReportingDescriptorBuilder::default() 45 | .id(rule.id.clone()) 46 | .short_description::( 47 | sarif::MultiformatMessageStringBuilder::default() 48 | .markdown(rule.title.clone().unwrap_or(rule.message.clone())) 49 | .text(rule.title.clone().unwrap_or(rule.message.clone())) 50 | .build()?, 51 | ) 52 | .full_description::( 53 | sarif::MultiformatMessageStringBuilder::default() 54 | .markdown(rule.message.clone()) 55 | .text(rule.message.clone()) 56 | .build()?, 57 | ) 58 | .help( 59 | sarif::MultiformatMessageStringBuilder::default() 60 | .markdown(rule.message.clone()) 61 | .text(rule.message.clone()) 62 | .build()?, 63 | ) 64 | .build()?; 65 | self.descriptors.push(descriptor); 66 | let idx = self.descriptors.len() - 1; 67 | self.descriptors_idx_map.insert(rule.id.clone(), idx); 68 | idx 69 | } 70 | }; 71 | 72 | let result = sarif::ResultBuilder::default() 73 | .rule_id(rule.id.clone()) 74 | .rule_index(descriptor_idx as i64) 75 | .message::( 76 | sarif::MessageBuilder::default() 77 | .markdown(rule.message.clone()) 78 | .text(rule.message.clone()) 79 | .build()?, 80 | ) 81 | .locations(vec![sarif::LocationBuilder::default() 82 | .physical_location( 83 | sarif::PhysicalLocationBuilder::default() 84 | .artifact_location( 85 | sarif::ArtifactLocationBuilder::default() 86 | .uri(target.relative_path()) 87 | .build()?, 88 | ) 89 | .region( 90 | sarif::RegionBuilder::default() 91 | .start_line(mitem.area.range::().start.row as i64) 92 | .start_column(mitem.area.range::().start.column as i64) 93 | .build()?, 94 | ) 95 | .build()?, 96 | ) 97 | .build()?]) 98 | .level( 99 | match rule.get_severity() { 100 | Severity::Unknown => sarif::ResultLevel::None, 101 | Severity::Low => sarif::ResultLevel::Note, 102 | Severity::Medium => sarif::ResultLevel::Warning, 103 | Severity::High => sarif::ResultLevel::Error, 104 | Severity::Critical => sarif::ResultLevel::Error, 105 | } 106 | .to_string(), 107 | ) 108 | .build()?; 109 | self.results.push(result); 110 | } 111 | 112 | Ok(()) 113 | } 114 | 115 | fn report(&mut self) -> Result<()> { 116 | let tool_component: sarif::ToolComponent = sarif::ToolComponentBuilder::default() 117 | .name("shisho") 118 | .version(env!("CARGO_PKG_VERSION")) 119 | .information_uri("https://docs.shisho.dev") 120 | .rules(self.descriptors.clone()) 121 | .build()?; 122 | 123 | let run = sarif::RunBuilder::default() 124 | .tool::(tool_component.try_into()?) 125 | .results(self.results.clone()) 126 | .build()?; 127 | 128 | let sarif = sarif::SarifBuilder::default() 129 | .version(sarif::Version::V2_1_0.to_string()) 130 | .schema("https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json") 131 | .runs(vec![run]) 132 | .build()?; 133 | 134 | let s = serde_json::to_string(&sarif)?; 135 | write!(self.writer, "{}", s)?; 136 | 137 | self.results = vec![]; 138 | self.descriptors = vec![]; 139 | self.descriptors_idx_map = HashMap::new(); 140 | Ok(()) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/cli/subcommand/check.rs: -------------------------------------------------------------------------------- 1 | //! This module defines `check` subcommand. 2 | 3 | use crate::cli::encoding::{parse_encoding, LABELS_SORTED}; 4 | use crate::cli::reporter::{ConsoleReporter, JSONReporter, Reporter, ReporterType, SARIFReporter}; 5 | use crate::cli::{CommonOpts, ReportOpts}; 6 | use crate::core::source::NormalizedSource; 7 | use crate::core::target::TargetLoader; 8 | use crate::core::tree::NormalizedTree; 9 | use crate::core::{ 10 | language::{Dockerfile, Go, Queryable, HCL}, 11 | ruleset::{self, Rule}, 12 | target::Target, 13 | tree::Tree, 14 | }; 15 | use ansi_term::Color; 16 | use anyhow::{anyhow, Result}; 17 | use encoding_rs::Encoding; 18 | use std::{collections::HashMap, convert::TryFrom}; 19 | use std::{iter::repeat, path::PathBuf}; 20 | use structopt::StructOpt; 21 | 22 | // Checks files under the given path with the given rule sets 23 | #[derive(StructOpt, Debug)] 24 | pub struct CheckOpts { 25 | /// Rule Set for searching 26 | #[structopt(parse(from_os_str))] 27 | pub ruleset_path: PathBuf, 28 | 29 | /// File path to search 30 | #[structopt(parse(from_os_str))] 31 | pub target_path: Option, 32 | 33 | #[structopt(flatten)] 34 | pub common: CommonOpts, 35 | 36 | #[structopt(short, long, parse(try_from_str = parse_encoding), possible_values(&LABELS_SORTED))] 37 | pub encoding: Option<&'static Encoding>, 38 | 39 | #[structopt(long)] 40 | pub exit_zero: bool, 41 | 42 | #[structopt(flatten)] 43 | pub report: ReportOpts, 44 | 45 | #[structopt(long)] 46 | pub exclude: Vec, 47 | } 48 | 49 | pub fn run(opts: CheckOpts) -> i32 { 50 | let exit_zero = opts.exit_zero; 51 | match handle_opts(opts) { 52 | Ok(total_findings) => { 53 | if total_findings > 0 && !exit_zero { 54 | 1 55 | } else { 56 | 0 57 | } 58 | } 59 | Err(e) => { 60 | eprintln!("{}: {}", Color::Red.paint("error"), e); 61 | 1 62 | } 63 | } 64 | } 65 | 66 | pub(crate) fn handle_opts(opts: CheckOpts) -> Result { 67 | let mut rule_map = HashMap::>::new(); 68 | 69 | let rulesets = ruleset::from_path(&opts.ruleset_path).map_err(|e| { 70 | anyhow!( 71 | "failed to load ruleset file {}: {}", 72 | opts.ruleset_path.as_os_str().to_string_lossy(), 73 | e 74 | ) 75 | })?; 76 | 77 | for ruleset in rulesets { 78 | for rule in ruleset.rules { 79 | if let Some(v) = rule_map.get_mut(&rule.language) { 80 | v.push(rule); 81 | } else { 82 | rule_map.insert(rule.language, vec![rule]); 83 | } 84 | } 85 | } 86 | 87 | let stdout = std::io::stdout(); 88 | let mut stdout = stdout.lock(); 89 | match opts.report.format { 90 | ReporterType::JSON => handle_rulemap( 91 | JSONReporter::new(&mut stdout), 92 | opts.target_path, 93 | opts.exclude, 94 | opts.encoding, 95 | rule_map, 96 | ), 97 | ReporterType::Console => handle_rulemap( 98 | ConsoleReporter::new(&mut stdout), 99 | opts.target_path, 100 | opts.exclude, 101 | opts.encoding, 102 | rule_map, 103 | ), 104 | ReporterType::SARIF => handle_rulemap( 105 | SARIFReporter::new(&mut stdout), 106 | opts.target_path, 107 | opts.exclude, 108 | opts.encoding, 109 | rule_map, 110 | ), 111 | } 112 | } 113 | 114 | pub(crate) fn handle_rulemap<'a>( 115 | mut reporter: impl Reporter<'a>, 116 | target_path: Option, 117 | 118 | exclude_path_pattern: Vec, 119 | encoding: Option<&'static Encoding>, 120 | rule_map: HashMap>, 121 | ) -> Result { 122 | let mut total_findings = 0; 123 | let loader = TargetLoader::new(exclude_path_pattern, encoding)?; 124 | match target_path { 125 | Some(p) => { 126 | for target in loader.from(p)? { 127 | if let Some(lang) = target.language() { 128 | if let Some(rules) = rule_map.get(&lang) { 129 | total_findings += handle_rules(&mut reporter, &target, rules, &lang)?; 130 | } 131 | } 132 | } 133 | } 134 | _ => { 135 | let target = loader.from_reader(std::io::stdin())?; 136 | for (lang, rules) in rule_map { 137 | total_findings += handle_rules(&mut reporter, &target, &rules, &lang)?; 138 | } 139 | } 140 | } 141 | 142 | reporter.report()?; 143 | Ok(total_findings) 144 | } 145 | 146 | fn handle_rules<'a, E: Reporter<'a>>( 147 | reporter: &mut E, 148 | target: &Target, 149 | rules: &[Rule], 150 | as_lang: &ruleset::Language, 151 | ) -> Result { 152 | match as_lang { 153 | ruleset::Language::HCL => handle_typed_rules::(reporter, target, rules), 154 | ruleset::Language::Dockerfile => { 155 | handle_typed_rules::(reporter, target, rules) 156 | } 157 | ruleset::Language::Go => handle_typed_rules::(reporter, target, rules), 158 | } 159 | } 160 | 161 | fn handle_typed_rules<'a, E: Reporter<'a>, Lang: Queryable>( 162 | reporter: &mut E, 163 | target: &Target, 164 | rules: &[Rule], 165 | ) -> Result { 166 | let source = NormalizedSource::from(target.body.as_str()); 167 | let tree = Tree::::try_from(source).unwrap(); 168 | let ptree = NormalizedTree::from(&tree); 169 | let ptree = ptree.as_ref_treeview(); 170 | 171 | let mut total_findings = 0; 172 | for rule in rules { 173 | let findings = rule.find::(&ptree)?; 174 | total_findings += findings.len(); 175 | reporter.add_entry::(target, repeat(rule).zip(findings).collect())?; 176 | } 177 | 178 | Ok(total_findings) 179 | } 180 | -------------------------------------------------------------------------------- /src/core/tree.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{ 2 | language::Queryable, 3 | matcher::{MatchedItem, TreeMatcher}, 4 | }; 5 | use anyhow::{anyhow, Result}; 6 | use std::{ 7 | collections::VecDeque, 8 | convert::{TryFrom, TryInto}, 9 | marker::PhantomData, 10 | }; 11 | 12 | use super::{ 13 | node::{Node, RootNode}, 14 | query::Query, 15 | source::NormalizedSource, 16 | }; 17 | 18 | pub struct Tree<'tree, T> { 19 | pub source: Vec, 20 | with_extra_newline: bool, 21 | 22 | tstree: tree_sitter::Tree, 23 | _marker: PhantomData<&'tree T>, 24 | } 25 | 26 | impl<'tree, T> Tree<'tree, T> 27 | where 28 | T: Queryable, 29 | { 30 | pub fn to_root_node(&'_ self) -> RootNode<'_> { 31 | RootNode::from_tstree(&self.tstree, &self.source, self.with_extra_newline) 32 | } 33 | } 34 | 35 | impl<'tree, T> TryFrom for Tree<'tree, T> 36 | where 37 | T: Queryable, 38 | { 39 | type Error = anyhow::Error; 40 | 41 | fn try_from(nsource: NormalizedSource) -> Result { 42 | let mut parser = tree_sitter::Parser::new(); 43 | parser 44 | .set_language(T::target_language()) 45 | .expect("Error loading hcl grammar"); 46 | 47 | let tstree = parser 48 | .parse(nsource.as_ref(), None) 49 | .ok_or(anyhow!("failed to load the code"))?; 50 | 51 | let with_extra_newline = nsource.with_extra_newline(); 52 | Ok(Tree { 53 | source: nsource.into(), 54 | with_extra_newline, 55 | 56 | tstree, 57 | _marker: PhantomData, 58 | }) 59 | } 60 | } 61 | 62 | impl<'tree, T> TryFrom<&str> for Tree<'tree, T> 63 | where 64 | T: Queryable, 65 | { 66 | type Error = anyhow::Error; 67 | 68 | fn try_from(nsource: &str) -> Result { 69 | let nsource = NormalizedSource::from(nsource); 70 | nsource.try_into() 71 | } 72 | } 73 | 74 | pub struct NormalizedTree<'tree, T> { 75 | pub view_root: Node<'tree>, 76 | pub source: &'tree [u8], 77 | _marker: PhantomData, 78 | } 79 | 80 | impl<'tree, T> NormalizedTree<'tree, T> 81 | where 82 | T: Queryable, 83 | { 84 | pub fn new(view_root: Node<'tree>, source: &'tree [u8]) -> NormalizedTree<'tree, T> { 85 | NormalizedTree { 86 | view_root, 87 | source, 88 | _marker: PhantomData, 89 | } 90 | } 91 | 92 | pub fn as_ref_treeview(&'tree self) -> RefTreeView<'tree, T> { 93 | self.into() 94 | } 95 | } 96 | 97 | impl<'tree, T> From<&'tree Tree<'tree, T>> for NormalizedTree<'tree, T> 98 | where 99 | T: Queryable, 100 | { 101 | fn from(t: &'tree Tree<'tree, T>) -> Self { 102 | NormalizedTree { 103 | view_root: t.to_root_node().into(), 104 | source: &t.source, 105 | _marker: PhantomData, 106 | } 107 | } 108 | } 109 | 110 | impl<'tree, T> From> for NormalizedTree<'tree, T> 111 | where 112 | T: Queryable, 113 | { 114 | fn from(t: Node<'tree>) -> Self { 115 | let source = t.source; 116 | NormalizedTree { 117 | view_root: t, 118 | source, 119 | _marker: PhantomData, 120 | } 121 | } 122 | } 123 | 124 | pub struct RefTreeView<'tree, T> { 125 | pub view_root: &'tree Node<'tree>, 126 | pub source: &'tree [u8], 127 | _marker: PhantomData, 128 | } 129 | 130 | impl<'tree, 'view, T> RefTreeView<'tree, T> 131 | where 132 | T: Queryable + 'tree, 133 | 'tree: 'view, 134 | { 135 | pub fn matches<'query>( 136 | &'view self, 137 | q: &'query Query<'query, T>, 138 | ) -> impl Iterator>> + 'query + 'view 139 | where 140 | 'tree: 'query, 141 | 'query: 'view, 142 | { 143 | TreeMatcher::new(self.traverse(), &q.pattern).filter_map(move |mut x| { 144 | let captures = match x.satisfies_all(q.constraints) { 145 | Ok((true, captures)) => captures, 146 | Ok((false, _)) => return None, 147 | Err(e) => { 148 | return Some(Err(anyhow::anyhow!( 149 | "failed to validate a match with constraints: {}", 150 | e 151 | ))) 152 | } 153 | }; 154 | x.captures.extend(captures); 155 | Some(Ok(x)) 156 | }) 157 | } 158 | 159 | pub fn traverse(&'view self) -> TreeTreverser<'tree> { 160 | TreeTreverser::new(self.view_root) 161 | } 162 | } 163 | 164 | impl<'tree, T> From<&'tree NormalizedTree<'tree, T>> for RefTreeView<'tree, T> 165 | where 166 | T: Queryable, 167 | { 168 | fn from(t: &'tree NormalizedTree<'tree, T>) -> Self { 169 | RefTreeView { 170 | view_root: &t.view_root, 171 | source: t.source, 172 | _marker: PhantomData, 173 | } 174 | } 175 | } 176 | 177 | impl<'tree, T> From<&'tree Node<'tree>> for RefTreeView<'tree, T> 178 | where 179 | T: Queryable, 180 | { 181 | fn from(t: &'tree Node<'tree>) -> Self { 182 | let source = t.source; 183 | RefTreeView { 184 | view_root: t, 185 | source, 186 | _marker: PhantomData, 187 | } 188 | } 189 | } 190 | 191 | pub struct TreeTreverser<'a> { 192 | queue: VecDeque<(usize, &'a Node<'a>)>, 193 | } 194 | 195 | impl<'a> TreeTreverser<'a> { 196 | #[inline] 197 | pub fn new(root: &'a Node<'a>) -> Self { 198 | Self { 199 | queue: VecDeque::from(vec![(0, root)]), 200 | } 201 | } 202 | } 203 | 204 | impl<'a> Iterator for TreeTreverser<'a> { 205 | type Item = (usize, &'a Node<'a>); 206 | 207 | #[inline] 208 | fn next(&mut self) -> Option { 209 | if let Some((depth, node)) = self.queue.pop_front() { 210 | let children = node.children.iter(); 211 | self.queue.extend(children.map(|child| (depth + 1, child))); 212 | 213 | Some((depth, node)) 214 | } else { 215 | None 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /docs/content/shisho/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Getting Started' 3 | metaTitle: 'Getting Started - Shisho' 4 | metaDescription: 'This page describes details of Shisho preparation.' 5 | --- 6 | 7 | # Overview 8 | 9 | Shisho enables you to analyze and transform your code. All you need to do is just two steps: 10 | 11 | 1. Set up your environment 12 | 2. Write a rule / rule set 13 | 14 | # Set up your environment 15 | 16 | The first step is setting up your environment. You have three options to run Shisho in your machine: 17 | 18 | - **Run with Docker (recommended)** 19 | - Run with a pre-built binary 20 | - Build from source code 21 | 22 | ## Run with Docker 23 | 24 | If you're familliar with Docker, all you need is `docker pull` like: 25 | 26 | ```sh 27 | docker pull ghcr.io/flatt-security/shisho-cli:latest 28 | ``` 29 | 30 | Then you'll find help message with the following command: 31 | 32 | ```sh 33 | docker run ghcr.io/flatt-security/shisho-cli --help 34 | ``` 35 | 36 | ## Run with a pre-built binary 37 | 38 | When you'd like to run shisho outside docker containers, please follow the instructions below. 39 | 40 | ### Linux / Windows via WSL 41 | 42 | Run the following commands to install: 43 | 44 | ```sh 45 | # Linux 46 | wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-unknown-linux-gnu.zip -O shisho.zip 47 | unzip shisho.zip 48 | chmod +x ./shisho 49 | mv ./shisho /usr/local/bin/shisho 50 | ``` 51 | 52 | If the pre-build binary is installed successfully, you'll see the help message with the following command: 53 | 54 | ```sh 55 | shisho --help 56 | ``` 57 | 58 | ### macOS 59 | 60 | Run the following commands to install: 61 | 62 | ``` 63 | # macOS 64 | wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-apple-darwin.zip -O shisho.zip 65 | unzip shisho.zip 66 | chmod +x ./shisho 67 | mv ./shisho /usr/local/bin/shisho 68 | ``` 69 | 70 | If the pre-build binary is installed successfully, you'll see the help message with the following command: 71 | 72 | ```sh 73 | shisho --help 74 | ``` 75 | 76 | > 📝 Tips: You can generate shell scripts for completion by running `shisho completion` command. 77 | 78 | ### Build from source code 79 | 80 | If you're a Rust developer, you can use `cargo` to install Shisho locally: 81 | 82 | ```sh 83 | git clone git@github.com:flatt-security/shisho.git 84 | cd shisho 85 | cargo install --path . 86 | ``` 87 | 88 | If succeeded, you'll see the help message by executing the following command: 89 | 90 | ```sh 91 | shisho --help 92 | ``` 93 | 94 | # Write a Rule / Rule Set 95 | 96 | The second step is writing a your own rule to analyze and transform your code. 97 | 98 | ## Search Code with `shisho find ` 99 | 100 | For example, Suppose you want to search something like "size = blah blah" from the following terraform code (`example.tf`): 101 | 102 | ```tf 103 | resource "aws_ebs_volume" "volume1" { 104 | availability_zone = "${var.region}a" 105 | size = 1 106 | } 107 | 108 | resource "aws_ebs_volume" "volume2" { 109 | availability_zone = "${var.region}a" 110 | size = 2 111 | } 112 | 113 | resource "aws_ebs_volume" "volume3" { 114 | availability_zone = "${var.region}a" 115 | size = 3 116 | } 117 | ``` 118 | 119 | Now with Shisho, you can search the code with `shisho find ` as follows, where `:[_]` is a _wildcard_: 120 | 121 | ```sh 122 | # with local binary 123 | cat example.tf | shisho find "size = :[_]" --lang hcl 124 | 125 | # with docker 126 | cat example.tf | docker run -i ghcr.io/flatt-security/shisho-cli:latest find "size = :[_]" --lang hcl 127 | ``` 128 | 129 | Run the commands above, then you'll see the following outputs: 130 | 131 | ``` 132 | [inline]: matched with the given rule 133 | In /dev/stdin: 134 | | 135 | 3 | size = 1 136 | | 137 | 138 | [inline]: matched with the given rule 139 | In /dev/stdin: 140 | | 141 | 8 | size = 2 142 | | 143 | 144 | [inline]: matched with the given rule 145 | In /dev/stdin: 146 | | 147 | 13 | size = 3 148 | | 149 | ``` 150 | 151 | ## Search Code with `shisho check ` 152 | 153 | When you repeat code search with the same pattern, you can write _a rule set_ (a set of _rules_) in YAML as follows: 154 | 155 | ```yaml 156 | version: '1' 157 | rules: 158 | - id: sample-policy 159 | language: hcl 160 | message: | 161 | here comes your own message 162 | pattern: | 163 | size = :[X] 164 | ``` 165 | 166 | A rule set can be used with `shisho check` command. Here's an example of searching code over `example.tf` with the rule set where `policy.yaml` is the aforementioned rule set: 167 | 168 | ```sh 169 | # with local binary 170 | shisho check policy.yaml example.tf 171 | 172 | # with docker 173 | docker run -i -v (pwd):/workspace ghcr.io/flatt-security/shisho-cli:latest check policy.yaml example.tf 174 | ``` 175 | 176 | ## Transform Code with Rewriting Pattern 177 | 178 | Suppose you'd like to rewrite all the `size = (blah blah)` with `size = 20`. Shisho works well in this situation; Now you can use the following commands to replace all the occurences of `size = (blah blah)` to `size = 20`: 179 | 180 | ```sh 181 | # with local binary 182 | cat example.tf | shisho find "size = :[_]" --rewrite "size = 20" --lang hcl 183 | 184 | # with docker 185 | cat example.tf | docker run -i ghcr.io/flatt-security/shisho-cli:latest find "size = :[_]" --rewrite "size = 20" --lang hcl 186 | ``` 187 | 188 | Run the command above, then you'll see the following outputs: 189 | 190 | ``` 191 | [inline]: matched with the given rule 192 | In /dev/stdin: 193 | | 194 | 3 | size = 1 195 | | 196 | Suggested changes: 197 | | - size = 1 198 | | + size = 20 199 | 200 | [inline]: matched with the given rule 201 | In /dev/stdin: 202 | | 203 | 8 | size = 2 204 | | 205 | Suggested changes: 206 | | - size = 2 207 | | + size = 20 208 | 209 | [inline]: matched with the given rule 210 | In /dev/stdin: 211 | | 212 | 13 | size = 3 213 | | 214 | Suggested changes: 215 | | - size = 3 216 | | + size = 20 217 | ``` 218 | 219 | This code transformation can be described by a rule set like: 220 | 221 | ```yaml 222 | version: '1' 223 | rules: 224 | - id: sample-policy 225 | language: hcl 226 | message: | 227 | here comes your own message 228 | pattern: | 229 | size = :[X] 230 | rewrite: size = 20 231 | ``` 232 | --------------------------------------------------------------------------------