diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..da45ec0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Every change to this repository needs the approval of a member of @kaitencloud/engineering. +# +# The default-branch ruleset requires a review from a code owner. With this file, that means an +# approval from one of the team's members -- never from a GitHub App or a workflow's token, +# which cannot belong to a team, and never from the pull request's own author. +* @kaitencloud/engineering diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ef37213 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +## What does this PR change? + + + +## Why? + + + +## Testing + + + +## Checklist + +- [ ] I have read `CONTRIBUTING.md`. +- [ ] Every commit in this PR includes a valid DCO `Signed-off-by` line. +- [ ] I have the right to submit all material in this PR. +- [ ] I have not included secrets or confidential data. +- [ ] I have updated tests where appropriate. +- [ ] I have updated documentation where appropriate. +- [ ] I have preserved required third-party licenses and attributions. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 754f6c4..03fd0c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ name: CI +# The default-branch ruleset requires three checks, named `lint`, `build` and `test`, as in +# Kaiten's SDK repositories. Each job below reports one of them. + on: push: branches: [ "main" ] @@ -7,8 +10,8 @@ on: branches: [ "main" ] jobs: - lint-build-test: - name: Lint, Build & Test + lint: + name: lint runs-on: ubuntu-latest steps: @@ -35,14 +38,42 @@ jobs: - name: Verify module metadata run: go mod tidy && git diff --exit-code -- go.mod go.sum - - name: Build - run: go build -v ./... - - - name: Test - run: go test -race -shuffle=on -cover ./... - # govulncheck reports only vulnerabilities in code the build actually reaches, so a # finding here is a real exposure in a shipped binary rather than a dependency-tree # coincidence. Pinned to match the version `task vuln` runs locally. - name: Vulnerability scan run: go run golang.org/x/vuln/cmd/govulncheck@v1.1.4 ./... + + build: + name: build + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25.x" + cache-dependency-path: go.sum + + - name: Build + run: go build -v ./... + + test: + name: test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25.x" + cache-dependency-path: go.sum + + - name: Test + run: go test -race -shuffle=on -cover ./... diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 0000000..75f10a8 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,113 @@ +name: DCO + +# Every commit of a pull request must be signed off by its author, as DCO.md and +# CONTRIBUTING.md ask: a `Signed-off-by: Name ` trailer naming the commit's author. +# Require the `DCO` check on main; this workflow is the source of truth. +# +# pull_request_target runs the workflow as it is on main, never as a pull request edits it, +# so a contributor cannot change the check that judges their own commits. It is safe because +# nothing here checks out or runs the pull request's code: the check below reads the commits' +# metadata from the API, and it needs no other file, so it can be copied to any repository. +on: + pull_request_target: + branches: ["main"] + +permissions: + contents: read + pull-requests: read + +jobs: + dco: + name: DCO + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Every commit is signed off by its author + shell: python + env: + GITHUB_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + """Fail when a commit of the pull request is not signed off by its author (DCO 1.1).""" + + import json + import os + import re + import sys + import urllib.request + + SIGN_OFF = re.compile( + r"^Signed-off-by:[ \t]*(?P.+?)[ \t]*<(?P[^<>]+)>[ \t]*$", re.M + ) + FIX = ( + "Sign commits off with `git commit -s`. To fix this pull request:\n\n" + " git commit --amend --signoff --no-edit # the latest commit\n" + " git rebase --signoff origin/main # every commit of the branch\n" + " git push --force-with-lease\n\n" + "See CONTRIBUTING.md and DCO.md." + ) + + + def problem(commit): + """Why a commit, as the API lists a pull request's commits, fails; None if it passes. + + A commit passes when one of its Signed-off-by trailers names its author, by name and + by email, ignoring case. A merge commit adds no work of its own, and a bot account + certifies nothing, so neither is checked. + """ + if len(commit.get("parents") or []) > 1: + return None + if (commit.get("author") or {}).get("type") == "Bot": + return None + author = commit["commit"]["author"] + signoffs = [(m["name"], m["email"]) for m in SIGN_OFF.finditer(commit["commit"]["message"])] + if any( + name.lower() == author["name"].lower() and email.lower() == author["email"].lower() + for name, email in signoffs + ): + return None + expected = f"{author['name']} <{author['email']}>" + if not signoffs: + return f"no Signed-off-by; expected {expected}" + found = ", ".join(f"{name} <{email}>" for name, email in signoffs) + return f"signed off by {found}, not by its author: expected {expected}" + + + def commits(): + """Every commit of the pull request, page by page.""" + api = os.environ.get("GITHUB_API_URL", "https://api.github.com") + repository, number = os.environ["GITHUB_REPOSITORY"], os.environ["PR_NUMBER"] + page = 1 + while True: + request = urllib.request.Request( + f"{api}/repos/{repository}/pulls/{number}/commits?per_page=100&page={page}", + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}", + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + with urllib.request.urlopen(request, timeout=30) as response: + batch = json.load(response) + yield from batch + if len(batch) < 100: + return + page += 1 + + + def main(): + listed = list(commits()) + failed = [(commit, why) for commit in listed if (why := problem(commit)) is not None] + for commit, why in failed: + subject = commit["commit"]["message"].splitlines()[0] + print(f"::error title=DCO::{commit['sha']} {subject}: {why}") + if failed: + print(f"\n{len(failed)} of {len(listed)} commits are not signed off by their author.") + print(f"\n{FIX}") + return 1 + print(f"All {len(listed)} commits are signed off by their author.") + return 0 + + + if __name__ == "__main__": + sys.exit(main()) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ded9a77..ccac2eb 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -32,6 +32,13 @@ archives: format_overrides: - goos: windows formats: [zip] + # Apache-2.0 asks every redistribution to carry LICENSE and NOTICE. GoReleaser includes + # LICENSE by default but not NOTICE; listing files replaces the defaults, so README is + # listed too. + files: + - LICENSE + - NOTICE + - README.md checksum: name_template: "checksums.txt" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f4708e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing to the Kaiten CLI + +Thank you for contributing to the Kaiten CLI. + +This project is licensed under the Apache License, Version 2.0. + +## Developer Certificate of Origin + +Kaiten uses the Developer Certificate of Origin 1.1 (DCO) for contributions. + +Every commit must include a `Signed-off-by` trailer matching the commit author. + +Use: + +```bash +git commit -s -m "Describe your change" +``` + +This produces: + +```text +Signed-off-by: Jane Doe +``` + +`git commit -s` is a **DCO sign-off**: it certifies the contribution under +[DCO.md](./DCO.md). `git commit -S`, with a capital S, is a **cryptographic +commit signature** made with a GPG or SSH key. They are not the same thing, and +the sign-off is what this project requires. There is nothing to install. + +### Fixing a missing sign-off + +A check named **DCO** verifies every commit of every pull request, and it is the +final word. If it fails, it names the commits to fix. + +For the latest commit: + +```bash +git commit --amend --signoff --no-edit +git push --force-with-lease +``` + +For several commits, sign off every commit of your branch at once: + +```bash +git rebase --signoff origin/main +git push --force-with-lease +``` + +## Contribution rules + +Please: + +- keep pull requests focused; +- add or update tests when behavior changes; +- update documentation when relevant; +- do not include secrets, customer data, or confidential information; +- do not submit code or assets that you do not have the right to contribute; +- preserve required third-party license and attribution notices. + +Accepted contributions are contributed under Apache-2.0. + +## Security + +Do not report unpatched vulnerabilities in public issues. + +See [SECURITY.md](./SECURITY.md). diff --git a/DCO.md b/DCO.md new file mode 100644 index 0000000..8201f99 --- /dev/null +++ b/DCO.md @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/LICENSE b/LICENSE index aa1c540..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -167,16 +168,26 @@ and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only - on Your own behalf and on behalf of Yourself, assuming sole - responsibility, not on behalf of any other Contributor, and only - if You agree to indemnify, defend, and hold each Contributor - harmless for any liability incurred by, or claims asserted against, - such Contributor by reason of your accepting any such warranty or - additional liability. + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - Copyright 2026 Kaiten + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..a728d73 --- /dev/null +++ b/NOTICE @@ -0,0 +1,3 @@ +Kaiten CLI + +Copyright 2026 KAITEN INC diff --git a/README.md b/README.md index 05930b7..2a42aef 100644 --- a/README.md +++ b/README.md @@ -231,4 +231,11 @@ stable, since scripts depend on them. ## License -Licensed under the [Apache License, Version 2.0](LICENSE). +The Kaiten CLI is open source and licensed under the +[Apache License, Version 2.0](./LICENSE). + +By contributing, you agree to certify your contribution under the +[Developer Certificate of Origin 1.1](./DCO.md). + +The Kaiten name and logos are not licensed under Apache-2.0. See the +[Kaiten trademark policy](https://github.com/kaitencloud/kaiten/blob/main/TRADEMARKS.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..314c5db --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +Please do not report security vulnerabilities through public GitHub issues. + +Report them privately, through either channel: + +- GitHub Private Vulnerability Reporting for this repository, from its + **Security** tab, when available; +- email to [security@kaiten.sh](mailto:security@kaiten.sh), which also suits + reporters without a GitHub account. + +Include, where possible: + +- affected version or commit; +- vulnerability description; +- reproduction steps or proof of concept; +- expected impact; +- known mitigations. + +KAITEN INC asks reporters to allow reasonable time to investigate and address +a vulnerability before public disclosure.