From f456f55de5246c8e444adb664253beb93434f2c6 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:45:02 +0800 Subject: [PATCH 01/19] feat: generate third-party notices --- THIRD_PARTY_NOTICES.md | 2139 +++++++++++++++++ go.mod | 2 +- go.sum | 4 +- .../third_party_notices/mit-package/LICENSE | 9 + .../mit-package/package.json | 1 + scripts/third_party_notices.py | 481 ++++ scripts/third_party_notices.test.py | 196 ++ 7 files changed, 2829 insertions(+), 3 deletions(-) create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 scripts/testdata/third_party_notices/mit-package/LICENSE create mode 100644 scripts/testdata/third_party_notices/mit-package/package.json create mode 100644 scripts/third_party_notices.py create mode 100644 scripts/third_party_notices.test.py diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000000..a7c525934b --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,2139 @@ +# Third-Party Notices + +## @clack/core 1.2.0 + +- Component: @clack/core +- Version: 1.2.0 +- Source: https://github.com/bombshell-dev/clack +- License: MIT +- Copyright: Copyright (c) Nate Moore + +### License Text +```text +MIT License + +Copyright (c) Nate Moore + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## @clack/prompts 1.2.0 + +- Component: @clack/prompts +- Version: 1.2.0 +- Source: https://github.com/bombshell-dev/clack +- License: MIT +- Copyright: Copyright (c) Nate Moore + +### License Text +```text +MIT License + +Copyright (c) Nate Moore + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## fast-string-truncated-width 1.2.1 + +- Component: fast-string-truncated-width +- Version: 1.2.1 +- Source: github:fabiospampinato/fast-string-truncated-width +- License: MIT +- Copyright: Copyright (c) 2024-present Fabio Spampinato + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2024-present Fabio Spampinato + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +``` + +## fast-string-width 1.1.0 + +- Component: fast-string-width +- Version: 1.1.0 +- Source: github:fabiospampinato/fast-string-width +- License: MIT +- Copyright: Copyright (c) 2024-present Fabio Spampinato + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2024-present Fabio Spampinato + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +``` + +## fast-wrap-ansi 0.1.6 + +- Component: fast-wrap-ansi +- Version: 0.1.6 +- Source: https://github.com/43081j/fast-wrap-ansi +- License: MIT +- Copyright: Copyright (c) 2025 James Garbutt +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +### License Text +```text +MIT License + +Copyright (c) 2025 James Garbutt + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/atotto/clipboard v0.1.4 + +- Component: github.com/atotto/clipboard +- Version: v0.1.4 +- Source: https://pkg.go.dev/github.com/atotto/clipboard@v0.1.4 +- License: BSD-3-Clause +- Copyright: Copyright (c) 2013 Ato Araki. All rights reserved. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright (c) 2013 Ato Araki. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of @atotto. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## github.com/aymanbagabas/go-osc52/v2 v2.0.1 + +- Component: github.com/aymanbagabas/go-osc52/v2 +- Version: v2.0.1 +- Source: https://pkg.go.dev/github.com/aymanbagabas/go-osc52/v2@v2.0.1 +- License: MIT +- Copyright: Copyright (c) 2022 Ayman Bagabas + +### License Text +```text +MIT License + +Copyright (c) 2022 Ayman Bagabas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/bmatcuk/doublestar/v4 v4.10.0 + +- Component: github.com/bmatcuk/doublestar/v4 +- Version: v4.10.0 +- Source: https://pkg.go.dev/github.com/bmatcuk/doublestar/v4@v4.10.0 +- License: MIT +- Copyright: Copyright (c) 2014 Bob Matcuk + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2014 Bob Matcuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/catppuccin/go v0.3.0 + +- Component: github.com/catppuccin/go +- Version: v0.3.0 +- Source: https://pkg.go.dev/github.com/catppuccin/go@v0.3.0 +- License: MIT +- Copyright: Copyright (c) 2021 Catppuccin + +### License Text +```text +MIT License + +Copyright (c) 2021 Catppuccin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 + +- Component: github.com/charmbracelet/bubbles +- Version: v0.21.1-0.20250623103423-23b8fd6302d7 +- Source: https://pkg.go.dev/github.com/charmbracelet/bubbles@v0.21.1-0.20250623103423-23b8fd6302d7 +- License: MIT +- Copyright: Copyright (c) 2020-2023 Charmbracelet, Inc + +### License Text +```text +MIT License + +Copyright (c) 2020-2023 Charmbracelet, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/bubbletea v1.3.6 + +- Component: github.com/charmbracelet/bubbletea +- Version: v1.3.6 +- Source: https://pkg.go.dev/github.com/charmbracelet/bubbletea@v1.3.6 +- License: MIT +- Copyright: Copyright (c) 2020-2023 Charmbracelet, Inc + +### License Text +```text +MIT License + +Copyright (c) 2020-2023 Charmbracelet, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc + +- Component: github.com/charmbracelet/colorprofile +- Version: v0.2.3-0.20250311203215-f60798e515dc +- Source: https://pkg.go.dev/github.com/charmbracelet/colorprofile@v0.2.3-0.20250311203215-f60798e515dc +- License: MIT +- Copyright: Copyright (c) 2020-2024 Charmbracelet, Inc + +### License Text +```text +MIT License + +Copyright (c) 2020-2024 Charmbracelet, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/huh v1.0.0 + +- Component: github.com/charmbracelet/huh +- Version: v1.0.0 +- Source: https://pkg.go.dev/github.com/charmbracelet/huh@v1.0.0 +- License: MIT +- Copyright: Copyright (c) 2023 Charm + +### License Text +```text +MIT License + +Copyright (c) 2023 Charm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/lipgloss v1.1.0 + +- Component: github.com/charmbracelet/lipgloss +- Version: v1.1.0 +- Source: https://pkg.go.dev/github.com/charmbracelet/lipgloss@v1.1.0 +- License: MIT +- Copyright: Copyright (c) 2021-2023 Charmbracelet, Inc + +### License Text +```text +MIT License + +Copyright (c) 2021-2023 Charmbracelet, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/x/ansi v0.9.3 + +- Component: github.com/charmbracelet/x/ansi +- Version: v0.9.3 +- Source: https://pkg.go.dev/github.com/charmbracelet/x/ansi@v0.9.3 +- License: MIT +- Copyright: Copyright (c) 2023 Charmbracelet, Inc. + +### License Text +```text +MIT License + +Copyright (c) 2023 Charmbracelet, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/x/cellbuf v0.0.13 + +- Component: github.com/charmbracelet/x/cellbuf +- Version: v0.0.13 +- Source: https://pkg.go.dev/github.com/charmbracelet/x/cellbuf@v0.0.13 +- License: MIT +- Copyright: Copyright (c) 2023 Charmbracelet, Inc. + +### License Text +```text +MIT License + +Copyright (c) 2023 Charmbracelet, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 + +- Component: github.com/charmbracelet/x/exp/strings +- Version: v0.0.0-20240722160745-212f7b056ed0 +- Source: https://pkg.go.dev/github.com/charmbracelet/x/exp/strings@v0.0.0-20240722160745-212f7b056ed0 +- License: MIT +- Copyright: Copyright (c) 2023 Charmbracelet, Inc. + +### License Text +```text +MIT License + +Copyright (c) 2023 Charmbracelet, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/charmbracelet/x/term v0.2.1 + +- Component: github.com/charmbracelet/x/term +- Version: v0.2.1 +- Source: https://pkg.go.dev/github.com/charmbracelet/x/term@v0.2.1 +- License: MIT +- Copyright: Copyright (c) 2023 Charmbracelet, Inc. + +### License Text +```text +MIT License + +Copyright (c) 2023 Charmbracelet, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/dustin/go-humanize v1.0.1 + +- Component: github.com/dustin/go-humanize +- Version: v1.0.1 +- Source: https://pkg.go.dev/github.com/dustin/go-humanize@v1.0.1 +- License: MIT +- Copyright: Copyright (c) 2005-2008 Dustin Sallings + +### License Text +```text +Copyright (c) 2005-2008 Dustin Sallings + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +``` + +## github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f + +- Component: github.com/erikgeiser/coninput +- Version: v0.0.0-20211004153227-1c3628e74d0f +- Source: https://pkg.go.dev/github.com/erikgeiser/coninput@v0.0.0-20211004153227-1c3628e74d0f +- License: MIT +- Copyright: Copyright (c) 2021 Erik G. + +### License Text +```text +MIT License + +Copyright (c) 2021 Erik G. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/gofrs/flock v0.8.1 + +- Component: github.com/gofrs/flock +- Version: v0.8.1 +- Source: https://pkg.go.dev/github.com/gofrs/flock@v0.8.1 +- License: BSD-3-Clause +- Copyright: Copyright (c) 2015-2020, Tim Heckman + +### License Text +```text +Copyright (c) 2015-2020, Tim Heckman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of gofrs nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## github.com/gogo/protobuf v1.3.2 + +- Component: github.com/gogo/protobuf +- Version: v1.3.2 +- Source: https://pkg.go.dev/github.com/gogo/protobuf@v1.3.2 +- License: BSD-3-Clause +- Copyright: Copyright (c) 2013, The GoGo Authors. All rights reserved. +Copyright 2010 The Go Authors. All rights reserved. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright (c) 2013, The GoGo Authors. All rights reserved. + +Protocol Buffers for Go with Gadgets + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## github.com/google/uuid v1.6.0 + +- Component: github.com/google/uuid +- Version: v1.6.0 +- Source: https://pkg.go.dev/github.com/google/uuid@v1.6.0 +- License: BSD-3-Clause +- Copyright: Copyright (c) 2009,2014 Google Inc. All rights reserved. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## github.com/gorilla/websocket v1.5.0 + +- Component: github.com/gorilla/websocket +- Version: v1.5.0 +- Source: https://pkg.go.dev/github.com/gorilla/websocket@v1.5.0 +- License: BSD-2-Clause +- Copyright: Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. + +### License Text +```text +Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## github.com/inconshreveable/mousetrap v1.1.0 + +- Component: github.com/inconshreveable/mousetrap +- Version: v1.1.0 +- Source: https://pkg.go.dev/github.com/inconshreveable/mousetrap@v1.1.0 +- License: Apache-2.0 +- Copyright: copyright notice that is included in or attached to the work +copyright license to reproduce, prepare Derivative Works of, +Copyright 2022 Alan Shreve (@inconshreveable) + +### License Text +```text + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + 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 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 + + 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 2022 Alan Shreve (@inconshreveable) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + +## github.com/itchyny/gojq v0.12.17 + +- Component: github.com/itchyny/gojq +- Version: v0.12.17 +- Source: https://pkg.go.dev/github.com/itchyny/gojq@v0.12.17 +- License: MIT +- Copyright: Copyright (c) 2019-2024 itchyny + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2019-2024 itchyny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/itchyny/timefmt-go v0.1.6 + +- Component: github.com/itchyny/timefmt-go +- Version: v0.1.6 +- Source: https://pkg.go.dev/github.com/itchyny/timefmt-go@v0.1.6 +- License: MIT +- Copyright: Copyright (c) 2020-2022 itchyny + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2020-2022 itchyny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/larksuite/oapi-sdk-go/v3 v3.7.2 + +- Component: github.com/larksuite/oapi-sdk-go/v3 +- Version: v3.7.2 +- Source: https://pkg.go.dev/github.com/larksuite/oapi-sdk-go/v3@v3.7.2 +- License: MIT +- Copyright: Copyright (c) 2020 Lark Technologies Pte. Ltd. + +### License Text +```text +MIT License + +Copyright (c) 2020 Lark Technologies Pte. Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/lucasb-eyer/go-colorful v1.2.0 + +- Component: github.com/lucasb-eyer/go-colorful +- Version: v1.2.0 +- Source: https://pkg.go.dev/github.com/lucasb-eyer/go-colorful@v1.2.0 +- License: MIT +- Copyright: Copyright (c) 2013 Lucas Beyer + +### License Text +```text +Copyright (c) 2013 Lucas Beyer + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## github.com/mattn/go-isatty v0.0.20 + +- Component: github.com/mattn/go-isatty +- Version: v0.0.20 +- Source: https://pkg.go.dev/github.com/mattn/go-isatty@v0.0.20 +- License: MIT +- Copyright: Copyright (c) Yasuhiro MATSUMOTO + +### License Text +```text +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 + +- Component: github.com/mattn/go-localereader +- Version: v0.0.2-0.20220822084749-2491eb6c1c75 +- Source: https://pkg.go.dev/github.com/mattn/go-localereader@v0.0.2-0.20220822084749-2491eb6c1c75 +- License: MIT +- Copyright: Copyright (c) 2022 Yasuhiro Matsumoto + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2022 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/mattn/go-runewidth v0.0.16 + +- Component: github.com/mattn/go-runewidth +- Version: v0.0.16 +- Source: https://pkg.go.dev/github.com/mattn/go-runewidth@v0.0.16 +- License: MIT +- Copyright: Copyright (c) 2016 Yasuhiro Matsumoto + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/Microsoft/go-winio v0.6.2 + +- Component: github.com/Microsoft/go-winio +- Version: v0.6.2 +- Source: https://pkg.go.dev/github.com/Microsoft/go-winio@v0.6.2 +- License: MIT +- Copyright: Copyright (c) 2015 Microsoft + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/mitchellh/hashstructure/v2 v2.0.2 + +- Component: github.com/mitchellh/hashstructure/v2 +- Version: v2.0.2 +- Source: https://pkg.go.dev/github.com/mitchellh/hashstructure/v2@v2.0.2 +- License: MIT +- Copyright: Copyright (c) 2016 Mitchell Hashimoto + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2016 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + +## github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 + +- Component: github.com/muesli/ansi +- Version: v0.0.0-20230316100256-276c6243b2f6 +- Source: https://pkg.go.dev/github.com/muesli/ansi@v0.0.0-20230316100256-276c6243b2f6 +- License: MIT +- Copyright: Copyright (c) 2021 Christian Muehlhaeuser + +### License Text +```text +MIT License + +Copyright (c) 2021 Christian Muehlhaeuser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/muesli/cancelreader v0.2.2 + +- Component: github.com/muesli/cancelreader +- Version: v0.2.2 +- Source: https://pkg.go.dev/github.com/muesli/cancelreader@v0.2.2 +- License: MIT +- Copyright: Copyright (c) 2022 Erik Geiser and Christian Muehlhaeuser + +### License Text +```text +MIT License + +Copyright (c) 2022 Erik Geiser and Christian Muehlhaeuser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/muesli/termenv v0.16.0 + +- Component: github.com/muesli/termenv +- Version: v0.16.0 +- Source: https://pkg.go.dev/github.com/muesli/termenv@v0.16.0 +- License: MIT +- Copyright: Copyright (c) 2019 Christian Muehlhaeuser + +### License Text +```text +MIT License + +Copyright (c) 2019 Christian Muehlhaeuser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/rivo/uniseg v0.4.7 + +- Component: github.com/rivo/uniseg +- Version: v0.4.7 +- Source: https://pkg.go.dev/github.com/rivo/uniseg@v0.4.7 +- License: MIT +- Copyright: Copyright (c) 2019 Oliver Kuederle + +### License Text +```text +MIT License + +Copyright (c) 2019 Oliver Kuederle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/sergi/go-diff v1.4.0 + +- Component: github.com/sergi/go-diff +- Version: v1.4.0 +- Source: https://pkg.go.dev/github.com/sergi/go-diff@v1.4.0 +- License: MIT +- Copyright: Copyright (c) 2012-2016 The go-diff Authors. All rights reserved. + +### License Text +```text +Copyright (c) 2012-2016 The go-diff Authors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +``` + +## github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + +- Component: github.com/skip2/go-qrcode +- Version: v0.0.0-20200617195104-da1b6568686e +- Source: https://pkg.go.dev/github.com/skip2/go-qrcode@v0.0.0-20200617195104-da1b6568686e +- License: MIT +- Copyright: Copyright (c) 2014 Tom Harwood + +### License Text +```text +Copyright (c) 2014 Tom Harwood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + +## github.com/spf13/cobra v1.10.2 + +- Component: github.com/spf13/cobra +- Version: v1.10.2 +- Source: https://pkg.go.dev/github.com/spf13/cobra@v1.10.2 +- License: Apache-2.0 +- Copyright: copyright notice that is included in or attached to the work +copyright license to reproduce, prepare Derivative Works of, + +### License Text +```text + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + 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 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. +``` + +## github.com/spf13/pflag v1.0.9 + +- Component: github.com/spf13/pflag +- Version: v1.0.9 +- Source: https://pkg.go.dev/github.com/spf13/pflag@v1.0.9 +- License: BSD-3-Clause +- Copyright: Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e + +- Component: github.com/xo/terminfo +- Version: v0.0.0-20220910002029-abceb7e1c41e +- Source: https://pkg.go.dev/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e +- License: MIT +- Copyright: Copyright (c) 2016 Anmol Sethi + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2016 Anmol Sethi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## github.com/zalando/go-keyring v0.2.8 + +- Component: github.com/zalando/go-keyring +- Version: v0.2.8 +- Source: https://pkg.go.dev/github.com/zalando/go-keyring@v0.2.8 +- License: MIT +- Copyright: Copyright (c) 2016 Zalando SE + +### License Text +```text +The MIT License (MIT) + +Copyright (c) 2016 Zalando SE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## golang.org/x/image v0.30.0 + +- Component: golang.org/x/image +- Version: v0.30.0 +- Source: https://pkg.go.dev/golang.org/x/image@v0.30.0 +- License: BSD-3-Clause +- Copyright: Copyright 2009 The Go Authors. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## golang.org/x/net v0.33.0 + +- Component: golang.org/x/net +- Version: v0.33.0 +- Source: https://pkg.go.dev/golang.org/x/net@v0.33.0 +- License: BSD-3-Clause +- Copyright: Copyright 2009 The Go Authors. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## golang.org/x/sync v0.16.0 + +- Component: golang.org/x/sync +- Version: v0.16.0 +- Source: https://pkg.go.dev/golang.org/x/sync@v0.16.0 +- License: BSD-3-Clause +- Copyright: Copyright 2009 The Go Authors. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## golang.org/x/sys v0.33.0 + +- Component: golang.org/x/sys +- Version: v0.33.0 +- Source: https://pkg.go.dev/golang.org/x/sys@v0.33.0 +- License: BSD-3-Clause +- Copyright: Copyright 2009 The Go Authors. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## golang.org/x/term v0.27.0 + +- Component: golang.org/x/term +- Version: v0.27.0 +- Source: https://pkg.go.dev/golang.org/x/term@v0.27.0 +- License: BSD-3-Clause +- Copyright: Copyright 2009 The Go Authors. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## golang.org/x/text v0.28.0 + +- Component: golang.org/x/text +- Version: v0.28.0 +- Source: https://pkg.go.dev/golang.org/x/text@v0.28.0 +- License: BSD-3-Clause +- Copyright: Copyright 2009 The Go Authors. +copyright notice, this list of conditions and the following disclaimer + +### License Text +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## gopkg.in/yaml.v3 v3.0.1 + +- Component: gopkg.in/yaml.v3 +- Version: v3.0.1 +- Source: https://pkg.go.dev/gopkg.in/yaml.v3@v3.0.1 +- License: Apache-2.0 OR MIT +- Copyright: copyright staring in 2011 when the project was ported over: +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov +Copyright (c) 2011-2019 Canonical Ltd +Copyright 2011-2016 Canonical Ltd. + +### License Text +```text + +This project is covered by two different licenses: MIT and Apache. + +#### MIT License #### + +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original MIT license, with the additional +copyright staring in 2011 when the project was ported over: + + apic.go emitterc.go parserc.go readerc.go scannerc.go + writerc.go yamlh.go yamlprivateh.go + +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +### Apache License ### + +All the remaining project files are covered by the Apache license: + +Copyright (c) 2011-2019 Canonical Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### NOTICE +```text +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +## sisteransi 1.0.5 + +- Component: sisteransi +- Version: 1.0.5 +- Source: https://github.com/terkelg/sisteransi +- License: MIT +- Copyright: Copyright (c) 2018 Terkel Gjervig Nielsen + +### License Text +```text +MIT License + +Copyright (c) 2018 Terkel Gjervig Nielsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/go.mod b/go.mod index 8839b7fc71..766c0750a5 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect diff --git a/go.sum b/go.sum index 574157832b..c131ef1221 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= +github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= diff --git a/scripts/testdata/third_party_notices/mit-package/LICENSE b/scripts/testdata/third_party_notices/mit-package/LICENSE new file mode 100644 index 0000000000..a84b21a389 --- /dev/null +++ b/scripts/testdata/third_party_notices/mit-package/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024 Fixture Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software. diff --git a/scripts/testdata/third_party_notices/mit-package/package.json b/scripts/testdata/third_party_notices/mit-package/package.json new file mode 100644 index 0000000000..782391db01 --- /dev/null +++ b/scripts/testdata/third_party_notices/mit-package/package.json @@ -0,0 +1 @@ +{"name":"fixture-mit","version":"1.2.3","license":"MIT","repository":"https://example.invalid/fixture-mit"} diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py new file mode 100644 index 0000000000..11842c803e --- /dev/null +++ b/scripts/third_party_notices.py @@ -0,0 +1,481 @@ +#!/usr/bin/env python3 +"""Generate a deterministic, fail-closed third-party notices document.""" + +from __future__ import annotations + +import argparse +import dataclasses +import json +import os +import re +import shutil +import stat +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Iterable + + +MAX_FILE_BYTES = 2 * 1024 * 1024 +MAX_TOTAL_BYTES = 16 * 1024 * 1024 +LICENSE_BASENAMES = ("LICENSE", "COPYING") +NOTICE_BASENAMES = ("NOTICE",) +PROHIBITED_LICENSE_WORDS = ("GPL", "LGPL", "AGPL", "SSPL", "GENERAL PUBLIC LICENSE", "SERVER SIDE PUBLIC") +RELEASE_TARGETS = ( + ("darwin", "amd64"), + ("darwin", "arm64"), + ("linux", "amd64"), + ("linux", "arm64"), + ("linux", "riscv64"), + ("windows", "amd64"), + ("windows", "arm64"), +) + + +class NoticeError(RuntimeError): + """A dependency cannot be safely included in a notices document.""" + + +@dataclasses.dataclass(frozen=True) +class Component: + name: str + version: str + source: str + license_id: str + copyright: str + license_text: str + notice_text: str = "" + + +@dataclasses.dataclass +class ReadBudget: + total: int = 0 + + def charge(self, size: int) -> None: + if size > MAX_FILE_BYTES: + raise NoticeError(f"dependency file exceeds {MAX_FILE_BYTES} byte limit") + self.total += size + if self.total > MAX_TOTAL_BYTES: + raise NoticeError(f"dependency files exceed {MAX_TOTAL_BYTES} byte total limit") + + +def _within(root: Path, target: Path) -> bool: + try: + return os.path.commonpath((str(root), str(target))) == str(root) + except ValueError: + return False + + +def _validate_dependency_path(root: Path, path: Path) -> Path: + """Reject symlinks and paths that resolve outside a dependency root.""" + root = root.absolute() + path = path.absolute() + if not _within(root, path): + raise NoticeError(f"dependency path escapes its root: {path}") + try: + relative = path.relative_to(root) + except ValueError as error: + raise NoticeError(f"dependency path escapes its root: {path}") from error + + current = root + for part in (".", *relative.parts): + if part != ".": + current = current / part + try: + mode = os.lstat(current).st_mode + except OSError as error: + raise NoticeError(f"cannot lstat dependency path: {current}") from error + if stat.S_ISLNK(mode): + raise NoticeError(f"symlinked dependency path is not allowed: {current}") + + resolved_root = Path(os.path.realpath(root)) + resolved_path = Path(os.path.realpath(path)) + if not _within(resolved_root, resolved_path): + raise NoticeError(f"dependency path resolves outside its root: {path}") + return resolved_path + + +def safe_read_text(root: Path, path: Path, budget: ReadBudget) -> str: + """Read one UTF-8 dependency file after containment and size validation.""" + resolved = _validate_dependency_path(root, path) + try: + file_stat = os.lstat(resolved) + except OSError as error: + raise NoticeError(f"cannot lstat dependency file: {path}") from error + if not stat.S_ISREG(file_stat.st_mode): + raise NoticeError(f"dependency file is not a regular file: {path}") + budget.charge(file_stat.st_size) + try: + with open(resolved, "rb") as handle: + data = handle.read(MAX_FILE_BYTES + 1) + except OSError as error: + raise NoticeError(f"cannot read dependency file: {path}") from error + if len(data) != file_stat.st_size or len(data) > MAX_FILE_BYTES: + raise NoticeError(f"dependency file changed while reading: {path}") + try: + return data.decode("utf-8") + except UnicodeDecodeError as error: + raise NoticeError(f"dependency file is not valid UTF-8: {path}") from error + + +def _read_json(root: Path, path: Path, budget: ReadBudget) -> dict: + try: + value = json.loads(safe_read_text(root, path, budget)) + except json.JSONDecodeError as error: + raise NoticeError(f"invalid JSON in dependency metadata: {path}") from error + if not isinstance(value, dict): + raise NoticeError(f"dependency metadata is not an object: {path}") + return value + + +def _allowed_document_name(name: str, basenames: Iterable[str]) -> bool: + upper_name = name.upper() + for basename in basenames: + if not upper_name.startswith(basename): + continue + if upper_name == basename: + return True + suffix = name[len(basename):] + if suffix.lower() in (".md", ".txt"): + return True + if suffix.startswith("-") and re.fullmatch(r"-[A-Za-z0-9._-]+", suffix): + return True + return False + + +def _document_text(root: Path, basenames: Iterable[str], budget: ReadBudget, required: bool) -> str: + documents = [] + _validate_dependency_path(root, root) + try: + candidates = sorted( + (path for path in root.iterdir() if _allowed_document_name(path.name, basenames)), + key=lambda path: path.name, + ) + except OSError as error: + raise NoticeError(f"cannot list dependency documents: {root}") from error + for candidate in candidates: + # lstat is deliberately performed even when this is the final path. + try: + os.lstat(candidate) + except FileNotFoundError: + continue + except OSError as error: + raise NoticeError(f"cannot inspect dependency document: {candidate}") from error + documents.append(safe_read_text(root, candidate, budget)) + if required and not documents: + raise NoticeError(f"missing required license document in {root}") + return "\n\n".join(documents) + + +def _reject_prohibited(value: str) -> None: + normalized = value.upper() + if any(word in normalized for word in PROHIBITED_LICENSE_WORDS): + raise NoticeError(f"prohibited license: {value}") + + +def _detect_bsd_license(license_text: str) -> str: + upper_text = license_text.upper() + if "ALL ADVERTISING MATERIALS" in upper_text: + raise NoticeError("unsupported BSD-4-Clause license") + if "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS" not in upper_text: + raise NoticeError("license cannot be identified as BSD-2-Clause or BSD-3-Clause") + if "NEITHER THE NAME OF" in upper_text: + return "BSD-3-Clause" + return "BSD-2-Clause" + + +def normalize_license_id(value: object, license_text: str) -> str: + """Return an allowed SPDX-like identifier, or fail closed.""" + declared = "" + if isinstance(value, str): + declared = value.strip() + elif isinstance(value, dict) and isinstance(value.get("type"), str): + declared = value["type"].strip() + if declared: + _reject_prohibited(declared) + normalized = declared.upper().replace(" ", "") + if normalized in {"MIT", "MITLICENSE"}: + return "MIT" + if normalized in {"ISC", "ISCLICENSE"}: + return "ISC" + if normalized.startswith("APACHE-2") or normalized in {"APACHE2.0", "APACHELICENSE2.0"}: + return "Apache-2.0" + if normalized.startswith("BSD-2"): + return "BSD-2-Clause" + if normalized.startswith("BSD-3"): + return "BSD-3-Clause" + if normalized in {"BSD", "BSDLICENSE"}: + return _detect_bsd_license(license_text) + raise NoticeError(f"unknown or unsupported license: {declared}") + + _reject_prohibited(license_text) + upper_text = license_text.upper() + detected = [] + if "APACHE LICENSE" in upper_text and "VERSION 2.0" in upper_text: + detected.append("Apache-2.0") + if "PERMISSION IS HEREBY GRANTED, FREE OF CHARGE" in upper_text: + detected.append("MIT") + if "PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE" in upper_text: + detected.append("ISC") + if "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS" in upper_text: + detected.append(_detect_bsd_license(license_text)) + if not detected: + raise NoticeError("license cannot be identified from dependency metadata or text") + return " OR ".join(sorted(detected)) + + +def _copyright_lines(*texts: str) -> str: + lines = [] + for text in texts: + lines.extend( + line.strip() + for line in text.splitlines() + if line.lstrip().lower().startswith("copyright") or line.lstrip().startswith("©") + ) + return "\n".join(dict.fromkeys(lines)) or "Not specified" + + +def _repository_source(metadata: dict, fallback: str) -> str: + repository = metadata.get("repository") + if isinstance(repository, dict): + repository = repository.get("url") + if not isinstance(repository, str) or not repository.strip(): + return fallback + source = repository.strip() + if source.startswith("git+"): + source = source[4:] + if source.endswith(".git"): + source = source[:-4] + return source + + +def component_from_node_package(package_dir: Path, budget: ReadBudget) -> Component: + metadata = _read_json(package_dir, package_dir / "package.json", budget) + name, version = metadata.get("name"), metadata.get("version") + if not isinstance(name, str) or not name or not isinstance(version, str) or not version: + raise NoticeError(f"missing name or version in dependency metadata: {package_dir}") + license_text = _document_text(package_dir, LICENSE_BASENAMES, budget, required=True) + notice_text = _document_text(package_dir, NOTICE_BASENAMES, budget, required=False) + license_id = normalize_license_id(metadata.get("license"), license_text) + return Component( + name=name, + version=version, + source=_repository_source(metadata, f"https://www.npmjs.com/package/{name}/v/{version}"), + license_id=license_id, + copyright=_copyright_lines(license_text, notice_text), + license_text=license_text, + notice_text=notice_text, + ) + + +def component_from_go_module(module: dict, budget: ReadBudget) -> Component: + name, version, directory = module.get("Path"), module.get("Version"), module.get("Dir") + if not isinstance(name, str) or not isinstance(version, str) or not isinstance(directory, str): + raise NoticeError("go list returned a module with missing path, version, or directory") + module_dir = Path(directory) + license_text = _document_text(module_dir, LICENSE_BASENAMES, budget, required=True) + notice_text = _document_text(module_dir, NOTICE_BASENAMES, budget, required=False) + license_id = normalize_license_id(None, license_text) + return Component( + name=name, + version=version, + source=f"https://pkg.go.dev/{name}@{version}", + license_id=license_id, + copyright=_copyright_lines(license_text, notice_text), + license_text=license_text, + notice_text=notice_text, + ) + + +def _parse_json_stream(value: str) -> list[dict]: + decoder = json.JSONDecoder() + position = 0 + records = [] + while position < len(value): + while position < len(value) and value[position].isspace(): + position += 1 + if position == len(value): + break + try: + record, position = decoder.raw_decode(value, position) + except json.JSONDecodeError as error: + raise NoticeError("invalid JSON from go list -m -json all") from error + if not isinstance(record, dict): + raise NoticeError("invalid module record from go list -m -json all") + records.append(record) + return records + + +def _go_runtime_module_records(repo_root: Path) -> list[dict]: + modules: dict[tuple[str, str], dict] = {} + with tempfile.TemporaryDirectory(prefix="third-party-notices-go-") as temporary: + temp_root = Path(temporary) + _copy_input_file(repo_root, temp_root, "go.mod") + _copy_input_file(repo_root, temp_root, "go.sum") + modfile = temp_root / "go.mod" + for goos, goarch in RELEASE_TARGETS: + environment = dict(os.environ, CGO_ENABLED="0", GOOS=goos, GOARCH=goarch) + try: + result = subprocess.run( + ["go", "list", "-mod=mod", f"-modfile={modfile}", "-deps", "-json", "."], + cwd=repo_root, + capture_output=True, + text=True, + check=True, + env=environment, + ) + except (OSError, subprocess.CalledProcessError) as error: + raise NoticeError(f"go list failed for {goos}/{goarch}") from error + for package in _parse_json_stream(result.stdout): + module = package.get("Module") + if not isinstance(module, dict) or module.get("Main"): + continue + name, version = module.get("Path"), module.get("Version") + if not isinstance(name, str) or not isinstance(version, str): + raise NoticeError(f"go list returned invalid module metadata for {goos}/{goarch}") + modules[(name, version)] = module + if not modules: + raise NoticeError("go list did not find any third-party runtime modules") + return [modules[key] for key in sorted(modules)] + + +def collect_go_components(repo_root: Path, budget: ReadBudget) -> list[Component]: + records = _go_runtime_module_records(repo_root) + missing_directories = [record for record in records if not isinstance(record.get("Dir"), str)] + if missing_directories: + try: + downloaded = subprocess.run( + ["go", "mod", "download", "-json", "all"], cwd=repo_root, capture_output=True, text=True, check=True + ) + except (OSError, subprocess.CalledProcessError) as error: + raise NoticeError("go mod download -json all failed while locating module source") from error + locations = { + (record.get("Path"), record.get("Version")): record.get("Dir") + for record in _parse_json_stream(downloaded.stdout) + if isinstance(record.get("Dir"), str) + } + for record in missing_directories: + directory = locations.get((record.get("Path"), record.get("Version"))) + if not isinstance(directory, str): + raise NoticeError(f"cannot locate module source for {record.get('Path')}@{record.get('Version')}") + record["Dir"] = directory + return [component_from_go_module(record, budget) for record in records] + + +def _copy_input_file(repo_root: Path, destination: Path, name: str) -> None: + source = repo_root / name + _validate_dependency_path(repo_root, source) + try: + shutil.copy2(source, destination / name) + except OSError as error: + raise NoticeError(f"cannot copy {name} into isolated npm directory") from error + + +def _node_package_directories(node_modules: Path) -> Iterable[Path]: + _validate_dependency_path(node_modules, node_modules) + for current, directories, filenames in os.walk(node_modules, topdown=True, followlinks=False): + current_path = Path(current) + kept = [] + for directory in directories: + path = current_path / directory + try: + is_link = stat.S_ISLNK(os.lstat(path).st_mode) + except OSError as error: + raise NoticeError(f"cannot inspect npm dependency directory: {path}") from error + if is_link: + raise NoticeError(f"symlinked npm dependency directory is not allowed: {path}") + kept.append(directory) + directories[:] = kept + if "package.json" not in filenames: + continue + parent = current_path.parent + is_unscoped = parent.name == "node_modules" + is_scoped = parent.parent.name == "node_modules" and parent.name.startswith("@") + if is_unscoped or is_scoped: + yield current_path + + +def collect_node_components(repo_root: Path, budget: ReadBudget) -> list[Component]: + with tempfile.TemporaryDirectory(prefix="third-party-notices-") as temporary: + temp_root = Path(temporary) + _copy_input_file(repo_root, temp_root, "package.json") + _copy_input_file(repo_root, temp_root, "package-lock.json") + try: + subprocess.run( + ["npm", "ci", "--ignore-scripts", "--omit=dev"], cwd=temp_root, capture_output=True, text=True, check=True + ) + except (OSError, subprocess.CalledProcessError) as error: + raise NoticeError("npm ci --ignore-scripts --omit=dev failed") from error + return [component_from_node_package(directory, budget) for directory in _node_package_directories(temp_root / "node_modules")] + + +def render_notices(components: Iterable[Component]) -> str: + lines = ["# Third-Party Notices", ""] + for component in sorted(components, key=lambda item: (item.name.lower(), item.name, item.version, item.source)): + lines.extend(( + f"## {component.name} {component.version}", + "", + f"- Component: {component.name}", + f"- Version: {component.version}", + f"- Source: {component.source}", + f"- License: {component.license_id}", + f"- Copyright: {component.copyright}", + "", + "### License Text", + "```text", + component.license_text.rstrip("\n"), + "```", + )) + if component.notice_text: + lines.extend(("", "### NOTICE", "```text", component.notice_text.rstrip("\n"), "```")) + lines.append("") + return "\n".join(lines) + + +def generate(repo_root: Path, output: Path) -> str: + budget = ReadBudget() + components = collect_go_components(repo_root, budget) + collect_node_components(repo_root, budget) + document = render_notices(components) + try: + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(document, encoding="utf-8", newline="\n") + except OSError as error: + raise NoticeError(f"cannot write output: {output}") from error + return document + + +def check(repo_root: Path, output: Path) -> None: + if not output.is_file(): + raise NoticeError(f"notices output does not exist: {output}") + with tempfile.TemporaryDirectory(prefix="third-party-notices-check-") as temporary: + generated = Path(temporary) / "THIRD_PARTY_NOTICES.md" + generate(repo_root, generated) + try: + expected = output.read_bytes() + actual = generated.read_bytes() + except OSError as error: + raise NoticeError("cannot read notices output for comparison") from error + if actual != expected: + raise NoticeError("third-party notices are out of date; run generate with --output") + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=("generate", "check")) + parser.add_argument("--output", required=True, type=Path, help="explicit path to THIRD_PARTY_NOTICES.md") + parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parent.parent) + args = parser.parse_args(argv) + try: + if args.command == "generate": + generate(args.repo_root.resolve(), args.output.resolve()) + else: + check(args.repo_root.resolve(), args.output.resolve()) + except NoticeError as error: + print(f"third_party_notices: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py new file mode 100644 index 0000000000..d42c94b414 --- /dev/null +++ b/scripts/third_party_notices.test.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +"""Behavior tests for the third-party notice generator.""" + +import importlib.util +import json +import shutil +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + + +SCRIPT = Path(__file__).with_name("third_party_notices.py") +SPEC = importlib.util.spec_from_file_location("third_party_notices", SCRIPT) +notices = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +sys.modules[SPEC.name] = notices +SPEC.loader.exec_module(notices) + +FIXTURES = Path(__file__).parent / "testdata" / "third_party_notices" +MIT_TEXT = """MIT License + +Copyright (c) 2024 Example Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the \"Software\"), to deal +in the Software without restriction. +""" +APACHE_TEXT = """Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ +""" +BSD_2_TEXT = """BSD 2-Clause License + +Copyright (c) 2024 Example Authors + +Redistribution and use in source and binary forms, with or without modification, are permitted. +""" +BSD_3_TEXT = """BSD 3-Clause License + +Copyright (c) 2024 Example Authors + +Redistribution and use in source and binary forms, with or without modification, are permitted. +Neither the name of Example Authors nor the names of its contributors may be used to endorse products. +""" + + +def make_package(root: Path, name: str, license_name: str, license_text: str) -> Path: + package = root / name + package.mkdir() + (package / "package.json").write_text( + '{"name": "' + name + '", "version": "1.0.0", "license": "' + license_name + '"}', + encoding="utf-8", + ) + (package / "LICENSE").write_text(license_text, encoding="utf-8") + return package + + +class ThirdPartyNoticesTests(unittest.TestCase): + def test_render_sorts_components_stably(self): + components = [ + notices.Component("zeta", "2.0.0", "https://z", "MIT", "Copyright Z", "z text"), + notices.Component("alpha", "1.0.0", "https://a", "ISC", "Copyright A", "a text"), + notices.Component("alpha", "0.9.0", "https://a", "ISC", "Copyright A", "old text"), + ] + + rendered = notices.render_notices(components) + + self.assertLess(rendered.index("## alpha 0.9.0"), rendered.index("## alpha 1.0.0")) + self.assertLess(rendered.index("## alpha 1.0.0"), rendered.index("## zeta 2.0.0")) + + def test_apache_notice_is_preserved_in_component(self): + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory), "apache-package", "Apache-2.0", APACHE_TEXT) + (package / "NOTICE").write_text("Example Apache NOTICE\n", encoding="utf-8") + + component = notices.component_from_node_package(package, notices.ReadBudget()) + + self.assertEqual(component.license_id, "Apache-2.0") + self.assertEqual(component.notice_text, "Example Apache NOTICE\n") + + def test_unknown_license_fails_closed(self): + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory), "unknown-package", "Proprietary", MIT_TEXT) + + with self.assertRaises(notices.NoticeError): + notices.component_from_node_package(package, notices.ReadBudget()) + + def test_bsd_license_is_classified_by_its_text(self): + self.assertEqual(notices.normalize_license_id("BSD", BSD_2_TEXT), "BSD-2-Clause") + self.assertEqual(notices.normalize_license_id("BSD", BSD_3_TEXT), "BSD-3-Clause") + + def test_runtime_module_collection_deduplicates_release_targets(self): + module = {"Path": "example.com/runtime", "Version": "v1.2.3", "Dir": "/tmp/runtime"} + main = {"Path": "github.com/larksuite/cli", "Main": True} + output = "\n".join(json.dumps(value) for value in (main, {"Module": module})) + completed = mock.Mock(stdout=output) + + with tempfile.TemporaryDirectory() as directory: + repo = Path(directory) + (repo / "go.mod").write_text("module example.com/project\n", encoding="utf-8") + (repo / "go.sum").write_text("", encoding="utf-8") + with mock.patch.object(notices.subprocess, "run", return_value=completed) as run: + records = notices._go_runtime_module_records(repo) + + self.assertEqual(records, [module]) + self.assertEqual(run.call_count, len(notices.RELEASE_TARGETS)) + self.assertEqual(run.call_args.kwargs["env"]["CGO_ENABLED"], "0") + + def test_license_hyphen_variant_is_accepted(self): + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory), "renamed-license-package", "MIT", MIT_TEXT) + (package / "LICENSE").rename(package / "LICENSE-MIT") + + component = notices.component_from_node_package(package, notices.ReadBudget()) + + self.assertEqual(component.license_id, "MIT") + + def test_symlinked_license_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + package = make_package(root, "symlink-package", "MIT", MIT_TEXT) + outside = root / "outside-license" + outside.write_text(MIT_TEXT, encoding="utf-8") + (package / "LICENSE").unlink() + (package / "LICENSE").symlink_to(outside) + + with self.assertRaises(notices.NoticeError): + notices.component_from_node_package(package, notices.ReadBudget()) + + def test_path_outside_dependency_root_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) / "dependency" + root.mkdir() + outside = Path(directory) / "outside" + outside.write_text("outside", encoding="utf-8") + + with self.assertRaises(notices.NoticeError): + notices.safe_read_text(root, outside, notices.ReadBudget()) + + def test_oversize_license_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory), "large-package", "MIT", MIT_TEXT) + (package / "LICENSE").write_bytes(b"x" * (notices.MAX_FILE_BYTES + 1)) + + with self.assertRaises(notices.NoticeError): + notices.component_from_node_package(package, notices.ReadBudget()) + + def test_total_read_limit_is_shared_between_components(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + budget = notices.ReadBudget() + text = ("Copyright (c) Example\nPermission is hereby granted, free of charge, to any person " + "obtaining a copy\n" + "x" * (notices.MAX_FILE_BYTES - 200_000)) + packages = [make_package(root, f"package-{index}", "MIT", text) for index in range(9)] + + for package in packages[:8]: + notices.component_from_node_package(package, budget) + with self.assertRaises(notices.NoticeError): + notices.component_from_node_package(packages[8], budget) + + def test_invalid_utf8_license_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory), "bad-utf8-package", "MIT", MIT_TEXT) + (package / "LICENSE").write_bytes(b"\xff\xfe\x00") + + with self.assertRaises(notices.NoticeError): + notices.component_from_node_package(package, notices.ReadBudget()) + + def test_fixture_is_safe_to_parse(self): + # A real, checked-in fixture catches accidental fixture path regressions. + with tempfile.TemporaryDirectory() as directory: + package = Path(directory) / "fixture" + shutil.copytree(FIXTURES / "mit-package", package) + component = notices.component_from_node_package(package, notices.ReadBudget()) + self.assertEqual((component.name, component.version, component.license_id), ("fixture-mit", "1.2.3", "MIT")) + + def test_check_compares_without_mutating_the_output(self): + component = notices.Component("example", "1.0.0", "https://example.invalid", "MIT", "Copyright", "text") + with tempfile.TemporaryDirectory() as directory: + output = Path(directory) / "THIRD_PARTY_NOTICES.md" + expected = notices.render_notices([component]).encode("utf-8") + output.write_bytes(expected) + before = output.stat() + with mock.patch.object(notices, "collect_go_components", return_value=[component]), \ + mock.patch.object(notices, "collect_node_components", return_value=[]): + notices.check(Path(directory), output) + after = output.stat() + self.assertEqual(output.read_bytes(), expected) + + self.assertEqual(before.st_mtime_ns, after.st_mtime_ns) + + +if __name__ == "__main__": + unittest.main() From 1b8eba077bf44529305327a2e5c0d72fe8472747 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:47:09 +0800 Subject: [PATCH 02/19] ci: require notices with dependency changes --- .githooks/pre-commit | 12 +++++++++ Makefile | 13 ++++++++- README.md | 4 +++ scripts/third_party_notices_hook.test.sh | 34 ++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100755 .githooks/pre-commit create mode 100755 scripts/third_party_notices_hook.test.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000000..56b9ad824d --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +set -euo pipefail + +staged_files="$(git diff --cached --name-only --diff-filter=ACMR)" +if printf '%s\n' "$staged_files" | grep -Eq '^(go\.mod|go\.sum|package\.json|package-lock\.json)$' \ + && ! printf '%s\n' "$staged_files" | grep -Fxq 'THIRD_PARTY_NOTICES.md'; then + echo "Dependency metadata changed. Run 'make third-party-notices', review it, and stage THIRD_PARTY_NOTICES.md." >&2 + exit 1 +fi diff --git a/Makefile b/Makefile index 671c0e69e2..133494aa03 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ PREFIX ?= /usr/local TEST_GOARCH := $(or $(GOARCH),$(shell go env GOARCH)) RACE_FLAG := $(if $(filter riscv64,$(TEST_GOARCH)),,-race) -.PHONY: all build vet fmt-check script-test test unit-test live-skills-test integration-test examples-build quality-gate install uninstall clean fetch_meta gitleaks sidecar-test +.PHONY: all build vet fmt-check script-test test unit-test live-skills-test integration-test examples-build quality-gate install uninstall clean fetch_meta gitleaks sidecar-test third-party-notices check-third-party-notices install-git-hooks all: test @@ -52,8 +52,19 @@ script-test: bash scripts/ci-workflow.test.sh bash scripts/release-workflow.test.sh bash scripts/semantic-review-workflow.test.sh + bash scripts/third_party_notices_hook.test.sh $(NODE) --test scripts/e2e_domains.test.js scripts/fetch_e2e_tat.test.js scripts/install.test.js scripts/release-preflight.test.js scripts/release-publish-policy.test.js scripts/semantic-review-verify-artifact.test.js scripts/pr-quality-summary.test.js scripts/semantic-review-publish.test.js scripts/ci-quality-summary-publish.test.js +third-party-notices: + python3 scripts/third_party_notices.py generate --output THIRD_PARTY_NOTICES.md + +check-third-party-notices: + python3 scripts/third_party_notices.py check --output THIRD_PARTY_NOTICES.md + +install-git-hooks: + git config core.hooksPath .githooks + @echo "Installed repository Git hooks." + # ./extension/... keeps the public plugin SDK in the default test matrix. unit-test: fetch_meta go test $(RACE_FLAG) -gcflags="all=-N -l" -count=1 \ diff --git a/README.md b/README.md index 9f23d1c935..b1e26ce199 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,10 @@ For major changes, we recommend discussing with us first via an Issue. Before opening a PR, see [AGENTS.md](./AGENTS.md) for the local build, test, and PR checklist used by contributors and AI agents. +### Third-party dependency notices + +Install the repository hook once with `make install-git-hooks`. When adding, removing, or upgrading a Go or npm production dependency, run `make third-party-notices`, review the generated `THIRD_PARTY_NOTICES.md`, and commit it with the dependency change. CI verifies the file independently; the hook is only a local reminder. + ## License This project is licensed under the **MIT License**. diff --git a/scripts/third_party_notices_hook.test.sh b/scripts/third_party_notices_hook.test.sh new file mode 100755 index 0000000000..99e8d25ceb --- /dev/null +++ b/scripts/third_party_notices_hook.test.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +mkdir -p "$TMP_DIR/.githooks" +cp "$ROOT_DIR/.githooks/pre-commit" "$TMP_DIR/.githooks/pre-commit" +chmod +x "$TMP_DIR/.githooks/pre-commit" + +git -C "$TMP_DIR" init --quiet +git -C "$TMP_DIR" config core.hooksPath .githooks +git -C "$TMP_DIR" config user.name "Third-Party Notices Test" +git -C "$TMP_DIR" config user.email "third-party-notices@example.invalid" + +printf 'module example.com/test\n' > "$TMP_DIR/go.mod" +git -C "$TMP_DIR" add go.mod +if git -C "$TMP_DIR" commit --quiet -m "test: missing notices"; then + echo "pre-commit should block staged dependency metadata without notices" >&2 + exit 1 +fi + +printf '# Third-Party Notices\n' > "$TMP_DIR/THIRD_PARTY_NOTICES.md" +git -C "$TMP_DIR" add THIRD_PARTY_NOTICES.md +git -C "$TMP_DIR" commit --quiet -m "test: include notices" + +if rg -q '^[[:space:]]*(make|python3?|git[[:space:]]+add)\b' "$ROOT_DIR/.githooks/pre-commit"; then + echo "pre-commit must only inspect the Git index; it must not execute or stage worktree code" >&2 + exit 1 +fi From 42712c2364062b05c9847bde8fbbd1da6c9cdd7c Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:51:01 +0800 Subject: [PATCH 03/19] ci: include notices in release packages --- .goreleaser.yml | 1 + package.json | 3 ++- scripts/build-pkg-pr-new.sh | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index f44e66c744..1befbf5d73 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -41,6 +41,7 @@ archives: - README.md - LICENSE - CHANGELOG.md + - THIRD_PARTY_NOTICES.md checksum: name_template: checksums.txt diff --git a/package.json b/package.json index c887366e66..2046bb542e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "scripts/install-wizard.js", "scripts/run.js", "checksums.txt", - "CHANGELOG.md" + "CHANGELOG.md", + "THIRD_PARTY_NOTICES.md" ], "dependencies": { "@clack/prompts": "^1.2.0" diff --git a/scripts/build-pkg-pr-new.sh b/scripts/build-pkg-pr-new.sh index edd6117693..855b0a86fe 100755 --- a/scripts/build-pkg-pr-new.sh +++ b/scripts/build-pkg-pr-new.sh @@ -96,12 +96,14 @@ cat > "$OUT_DIR/package.json" < Date: Mon, 10 Aug 2026 17:55:05 +0800 Subject: [PATCH 04/19] ci: gate releases on third-party notices --- .github/workflows/ci.yml | 8 +++++ .github/workflows/pkg-pr-new.yml | 16 +++++++-- .github/workflows/release.yml | 54 +++++++++++++++++++++++++++-- Makefile | 1 + scripts/ci-workflow.test.sh | 15 ++++++++ scripts/pkg-pr-new-workflow.test.sh | 23 ++++++++++++ scripts/release-workflow.test.sh | 34 +++++++++++++++--- 7 files changed, 141 insertions(+), 10 deletions(-) create mode 100755 scripts/pkg-pr-new-workflow.test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7157f3d366..00b028d00f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -488,6 +488,10 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: '3.x' + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: '22.14.0' + package-manager-cache: false - name: Fetch meta data run: python3 scripts/fetch_meta.py - name: Gitleaks @@ -501,6 +505,10 @@ jobs: run: go run golang.org/x/vuln/cmd/govulncheck@v1.1.4 ./... - name: Check dependency licenses run: go run github.com/google/go-licenses/v2@v2.0.1 check ./... --disallowed_types=forbidden,restricted,reciprocal,unknown + - name: Install pinned npm + run: npm install --global npm@11.16.0 + - name: Check third-party notices + run: make check-third-party-notices license-header: if: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index a6582fa529..398f88fe8d 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -12,6 +12,7 @@ jobs: publish: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -20,9 +21,20 @@ jobs: with: go-version-file: go.mod - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: - node-version: lts/* + python-version: '3.x' + + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: '22.14.0' + package-manager-cache: false + + - name: Install pinned npm + run: npm install --global npm@11.16.0 + + - name: Check third-party notices + run: make check-third-party-notices - name: Build preview package run: ./scripts/build-pkg-pr-new.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63fdba0553..ddf80b9235 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,9 +85,35 @@ jobs: fs.appendFileSync(output, `source_sha=${sourceSha}\nversion=${result.data.tagVersion}\nchannel=${channel}\nprerelease=${channel === "beta"}\n`); NODE - build-sign-notarize: + license-compliance: needs: preflight runs-on: ubuntu-22.04 + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + ref: ${{ needs.preflight.outputs.source_sha }} + persist-credentials: false + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version: '1.23' + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.x' + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: '22.14.0' + package-manager-cache: false + - name: Install pinned npm + run: npm install --global npm@11.16.0 + - name: Check third-party notices + run: make check-third-party-notices + + build-sign-notarize: + needs: [preflight, license-compliance] + runs-on: ubuntu-22.04 timeout-minutes: 45 # This is the single approval boundary for the release. It grants access to # Apple signing material before the candidate is built. @@ -97,6 +123,7 @@ jobs: steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: + ref: ${{ needs.preflight.outputs.source_sha }} fetch-depth: 0 persist-credentials: false - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 @@ -111,6 +138,15 @@ jobs: registry-url: 'https://registry.npmjs.org' package-manager-cache: false + - name: Verify tag still points to source commit + env: + SOURCE_SHA: ${{ needs.preflight.outputs.source_sha }} + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + git fetch --no-tags origin "refs/tags/${TAG}:refs/tags/${TAG}" + [[ "$(git rev-parse "refs/tags/${TAG}^{commit}")" == "$SOURCE_SHA" ]] || { echo "Release tag changed after preflight." >&2; exit 1; } + - name: Fetch build metadata run: python3 scripts/fetch_meta.py @@ -168,6 +204,12 @@ jobs: run: | set -euo pipefail (cd dist && sha256sum --check checksums.txt) + for archive in dist/*.tar.gz; do + tar -tzf "$archive" | grep -Eq '(^|/)THIRD_PARTY_NOTICES\.md$' || { echo "${archive} is missing THIRD_PARTY_NOTICES.md." >&2; exit 1; } + done + for archive in dist/*.zip; do + unzip -Z1 "$archive" | grep -Eq '(^|/)THIRD_PARTY_NOTICES\.md$' || { echo "${archive} is missing THIRD_PARTY_NOTICES.md." >&2; exit 1; } + done mkdir release-candidate cp dist/*.tar.gz dist/*.zip dist/checksums.txt release-candidate/ cp dist/CHANGELOG.md release-candidate/CHANGELOG.md @@ -181,6 +223,9 @@ jobs: throw new Error("npm pack did not produce the expected release package"); } NODE + npm_archive="$(find release-candidate -maxdepth 1 -type f -name '*.tgz' -print -quit)" + [[ -n "$npm_archive" ]] || { echo "npm pack did not create a tarball." >&2; exit 1; } + tar -tzf "$npm_archive" | grep -Fxq 'package/THIRD_PARTY_NOTICES.md' || { echo "npm package is missing THIRD_PARTY_NOTICES.md." >&2; exit 1; } - name: Upload release candidate uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 @@ -523,8 +568,8 @@ jobs: esac retry-guidance: - needs: [preflight, build-sign-notarize, create-draft-release, verify-macos, publish-github, publish-npm] - if: ${{ always() && (needs.preflight.result == 'failure' || needs.build-sign-notarize.result == 'failure' || needs.create-draft-release.result == 'failure' || needs.verify-macos.result == 'failure' || needs.publish-github.result == 'failure' || needs.publish-npm.result == 'failure') }} + needs: [preflight, license-compliance, build-sign-notarize, create-draft-release, verify-macos, publish-github, publish-npm] + if: ${{ always() && (needs.preflight.result == 'failure' || needs.license-compliance.result == 'failure' || needs.build-sign-notarize.result == 'failure' || needs.create-draft-release.result == 'failure' || needs.verify-macos.result == 'failure' || needs.publish-github.result == 'failure' || needs.publish-npm.result == 'failure') }} runs-on: ubuntu-22.04 permissions: contents: read @@ -532,6 +577,7 @@ jobs: - name: Write retry guidance env: PREFLIGHT_RESULT: ${{ needs.preflight.result }} + LICENSE_RESULT: ${{ needs.license-compliance.result }} BUILD_RESULT: ${{ needs.build-sign-notarize.result }} DRAFT_RESULT: ${{ needs.create-draft-release.result }} VERIFY_RESULT: ${{ needs.verify-macos.result }} @@ -547,6 +593,7 @@ jobs: echo "| Job | Result |" echo "| --- | --- |" echo "| preflight | ${PREFLIGHT_RESULT} |" + echo "| license-compliance | ${LICENSE_RESULT} |" echo "| build-sign-notarize | ${BUILD_RESULT} |" echo "| create-draft-release | ${DRAFT_RESULT} |" echo "| verify-macos | ${VERIFY_RESULT} |" @@ -557,6 +604,7 @@ jobs: 'Select the recovery action from the failed-step diagnosis. If a Draft or public Release already exists, use **Re-run failed jobs**, not **Re-run all jobs**:' \ '' \ '- **preflight:** network or fetch failure → retry preflight. Version/tag validation failure → correct it, then create a new tag.' \ + '- **license-compliance:** dependency registry failure → retry this job. Missing, unknown, or disallowed license → update the dependency or notices, then create a new tag.' \ '- **build-sign-notarize:** transient build/service failure → retry build. Code or release configuration issue → correct it, then create a new tag.' \ '- **create-draft-release:** GitHub Draft Release API/upload failure → retry draft; a partial Draft is verified and only missing assets are uploaded. Release-candidate inconsistency → delete the Draft, then retry build.' \ '- **verify-macos:** runner or network failure → retry only the failed matrix child. Checksum, signing, notarization, or runtime failure → delete the Draft, then retry build.' \ diff --git a/Makefile b/Makefile index 133494aa03..7c52173996 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ fmt-check: script-test: bash scripts/resolve-changed-from.test.sh bash scripts/ci-workflow.test.sh + bash scripts/pkg-pr-new-workflow.test.sh bash scripts/release-workflow.test.sh bash scripts/semantic-review-workflow.test.sh bash scripts/third_party_notices_hook.test.sh diff --git a/scripts/ci-workflow.test.sh b/scripts/ci-workflow.test.sh index e74d525de8..0cda218e15 100644 --- a/scripts/ci-workflow.test.sh +++ b/scripts/ci-workflow.test.sh @@ -130,6 +130,21 @@ if grep -Fq '${{ secrets.' <<<"$deterministic_section"; then exit 1 fi +if ! grep -Fq 'actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38' <<<"$security_section"; then + echo "security job must install the pinned Node runtime for third-party notices" >&2 + exit 1 +fi + +if ! grep -Fq "node-version: '22.14.0'" <<<"$security_section"; then + echo "security job must use Node 22.14.0 when checking third-party notices" >&2 + exit 1 +fi + +if ! grep -Fq 'npm install --global npm@11.16.0' <<<"$security_section" || ! grep -Fq 'make check-third-party-notices' <<<"$security_section"; then + echo "security job must verify generated third-party notices with pinned npm" >&2 + exit 1 +fi + if ! grep -Fq "Run CLI deterministic gate" <<<"$deterministic_section"; then echo "deterministic-gate should run the CLI deterministic gate step" exit 1 diff --git a/scripts/pkg-pr-new-workflow.test.sh b/scripts/pkg-pr-new-workflow.test.sh new file mode 100755 index 0000000000..855c8fd26d --- /dev/null +++ b/scripts/pkg-pr-new-workflow.test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +set -euo pipefail + +ruby -ryaml <<'RUBY' +workflow = YAML.load_file(".github/workflows/pkg-pr-new.yml") +abort("pkg.pr.new workflow must have read-only contents permission") unless workflow.fetch("permissions") == { "contents" => "read" } + +job = workflow.fetch("jobs").fetch("publish") +abort("pkg.pr.new workflow must time out") unless job.fetch("timeout-minutes") == 15 +abort("pkg.pr.new workflow must not reference secrets") if job.to_s.include?("secrets.") + +steps = job.fetch("steps") +notices = steps.index { |step| step["name"] == "Check third-party notices" } +build = steps.index { |step| step["name"] == "Build preview package" } +publish = steps.index { |step| step["name"] == "Publish to pkg.pr.new" } +abort("pkg.pr.new must check notices before building") unless notices && build && notices < build +abort("pkg.pr.new must check notices before publishing") unless notices < publish +abort("pkg.pr.new must use Node 22.14.0") unless steps.any? { |step| step.dig("with", "node-version") == "22.14.0" } +abort("pkg.pr.new must pin npm 11.16.0") unless steps.any? { |step| step["run"] == "npm install --global npm@11.16.0" } +RUBY diff --git a/scripts/release-workflow.test.sh b/scripts/release-workflow.test.sh index b524fbc9fc..d53ca6521a 100755 --- a/scripts/release-workflow.test.sh +++ b/scripts/release-workflow.test.sh @@ -6,7 +6,7 @@ set -euo pipefail # This verifies the release workflow's declarative contract. The shell commands # inside individual steps are exercised by the beta release rehearsal instead. -ruby -ropen3 -ryaml <<'RUBY' +ruby -rjson -ropen3 -ryaml <<'RUBY' workflow = YAML.load_file(".github/workflows/release.yml") goreleaser = YAML.load_file(".goreleaser.yml") @@ -49,7 +49,7 @@ jobs.each do |job_name, job| end end -expected_jobs = %w[preflight build-sign-notarize create-draft-release verify-macos publish-github publish-npm retry-guidance] +expected_jobs = %w[preflight license-compliance build-sign-notarize create-draft-release verify-macos publish-github publish-npm retry-guidance] expect_equal(jobs.keys.sort, expected_jobs.sort, "release jobs") expect_equal(workflow.fetch("concurrency"), { @@ -59,12 +59,13 @@ expect_equal(workflow.fetch("concurrency"), { expected_needs = { "preflight" => nil, - "build-sign-notarize" => "preflight", + "license-compliance" => "preflight", + "build-sign-notarize" => %w[preflight license-compliance], "create-draft-release" => %w[preflight build-sign-notarize], "verify-macos" => %w[preflight build-sign-notarize create-draft-release], "publish-github" => %w[preflight create-draft-release verify-macos], "publish-npm" => %w[preflight build-sign-notarize publish-github], - "retry-guidance" => %w[preflight build-sign-notarize create-draft-release verify-macos publish-github publish-npm], + "retry-guidance" => %w[preflight license-compliance build-sign-notarize create-draft-release verify-macos publish-github publish-npm], } expected_needs.each do |job_name, needs| expect_equal(jobs.fetch(job_name)["needs"], needs, "#{job_name} dependencies") @@ -72,6 +73,7 @@ end expected_permissions = { "preflight" => { "contents" => "read" }, + "license-compliance" => { "contents" => "read" }, "build-sign-notarize" => { "contents" => "read" }, "create-draft-release" => { "contents" => "write" }, "verify-macos" => { "contents" => "read" }, @@ -84,6 +86,7 @@ expected_permissions.each do |job_name, permissions| end expected_timeouts = { + "license-compliance" => 15, "build-sign-notarize" => 45, "create-draft-release" => 15, "verify-macos" => 20, @@ -103,7 +106,7 @@ expect_equal(jobs.fetch("publish-npm").fetch("concurrency"), { }, "npm publication concurrency") retry_guidance = jobs.fetch("retry-guidance") -retry_condition = "${{ always() && (needs.preflight.result == 'failure' || needs.build-sign-notarize.result == 'failure' || needs.create-draft-release.result == 'failure' || needs.verify-macos.result == 'failure' || needs.publish-github.result == 'failure' || needs.publish-npm.result == 'failure') }}" +retry_condition = "${{ always() && (needs.preflight.result == 'failure' || needs.license-compliance.result == 'failure' || needs.build-sign-notarize.result == 'failure' || needs.create-draft-release.result == 'failure' || needs.verify-macos.result == 'failure' || needs.publish-github.result == 'failure' || needs.publish-npm.result == 'failure') }}" expect_equal(retry_guidance.fetch("if"), retry_condition, "retry guidance failure condition") expect_equal(retry_guidance.fetch("runs-on"), "ubuntu-22.04", "retry guidance runner") @@ -116,6 +119,16 @@ contract_error("retry guidance must direct recoveries to failed-job retries") un contract_error("retry guidance must explain Draft cleanup before a rebuild") unless retry_step.fetch("run").include?("delete the Draft, then retry build") contract_error("retry guidance must explain public Release cleanup after npm policy rejection") unless retry_step.fetch("run").include?("delete the public GitHub Release") +license_compliance = jobs.fetch("license-compliance") +contract_error("license-compliance must not request an Environment") if license_compliance.key?("environment") +contract_error("license-compliance must not reference secrets") if scalar_values(license_compliance).grep(String).any? { |value| value.include?("secrets.") } +license_checkout = license_compliance.fetch("steps").first +expect_equal(license_checkout.dig("with", "ref"), "${{ needs.preflight.outputs.source_sha }}", "license-compliance checkout ref") +expect_equal(license_compliance.fetch("steps").last.fetch("run"), "make check-third-party-notices", "license-compliance command") + +build_checkout = jobs.fetch("build-sign-notarize").fetch("steps").first +expect_equal(build_checkout.dig("with", "ref"), "${{ needs.preflight.outputs.source_sha }}", "build checkout ref") + signing_references = %w[ secrets.MACOS_SIGN_P12 secrets.MACOS_SIGN_PASSWORD @@ -145,6 +158,11 @@ fetch_metadata_index = build_steps.index { |step| step["name"] == "Fetch build m prepare_key_index = build_steps.index { |step| step["name"] == "Prepare Apple notarization key" } contract_error("build metadata must be fetched before Apple credentials are prepared") unless fetch_metadata_index && prepare_key_index && fetch_metadata_index < prepare_key_index contract_error("build metadata must be fetched outside GoReleaser hooks") if goreleaser.dig("before", "hooks")&.include?("python3 scripts/fetch_meta.py") +contract_error("build must recheck that the release tag still resolves to source_sha") unless build_steps.any? { |step| step["name"] == "Verify tag still points to source commit" } +candidate_step = build_steps.find { |step| step["name"] == "Build release candidate" } +candidate_run = candidate_step&.fetch("run", nil) +contract_error("candidate archives must contain third-party notices") unless candidate_run&.include?("THIRD_PARTY_NOTICES") +contract_error("candidate npm package must contain third-party notices") unless candidate_run&.include?("package/THIRD_PARTY_NOTICES.md") macos = jobs.fetch("verify-macos") expect_equal(macos.fetch("strategy").fetch("matrix").fetch("include"), [ @@ -207,6 +225,12 @@ expect_equal(macos_notarize.fetch("notarize"), { "timeout" => "20m", }, "macOS notarization inputs") contract_error("GoReleaser must build a darwin release artifact") unless goreleaser.fetch("builds").any? { |build| build.fetch("goos").include?("darwin") } +contract_error("GoReleaser archives must include third-party notices") unless goreleaser.fetch("archives").all? { |archive| archive.fetch("files").include?("THIRD_PARTY_NOTICES.md") } + +npm_package = JSON.parse(File.read("package.json")) +contract_error("npm package must include third-party notices") unless npm_package.fetch("files").include?("THIRD_PARTY_NOTICES.md") +preview_package = File.read("scripts/build-pkg-pr-new.sh") +contract_error("preview package must include third-party notices") unless preview_package.include?("THIRD_PARTY_NOTICES.md") puts "release workflow contract passed" RUBY From 70430e189ad96b73dba026bae8f915c91b128d6b Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:56:47 +0800 Subject: [PATCH 05/19] fix: validate declared dependency licenses --- scripts/third_party_notices.py | 54 +++++++++++++++++------------ scripts/third_party_notices.test.py | 4 +++ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 11842c803e..20127083e0 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -185,6 +185,21 @@ def _detect_bsd_license(license_text: str) -> str: return "BSD-2-Clause" +def _detect_license_ids(license_text: str) -> set[str]: + _reject_prohibited(license_text) + upper_text = license_text.upper() + detected = set() + if "APACHE LICENSE" in upper_text and "VERSION 2.0" in upper_text: + detected.add("Apache-2.0") + if "PERMISSION IS HEREBY GRANTED, FREE OF CHARGE" in upper_text: + detected.add("MIT") + if "PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE" in upper_text: + detected.add("ISC") + if "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS" in upper_text: + detected.add(_detect_bsd_license(license_text)) + return detected + + def normalize_license_id(value: object, license_text: str) -> str: """Return an allowed SPDX-like identifier, or fail closed.""" declared = "" @@ -192,34 +207,27 @@ def normalize_license_id(value: object, license_text: str) -> str: declared = value.strip() elif isinstance(value, dict) and isinstance(value.get("type"), str): declared = value["type"].strip() + detected = _detect_license_ids(license_text) if declared: _reject_prohibited(declared) normalized = declared.upper().replace(" ", "") if normalized in {"MIT", "MITLICENSE"}: - return "MIT" - if normalized in {"ISC", "ISCLICENSE"}: - return "ISC" - if normalized.startswith("APACHE-2") or normalized in {"APACHE2.0", "APACHELICENSE2.0"}: - return "Apache-2.0" - if normalized.startswith("BSD-2"): - return "BSD-2-Clause" - if normalized.startswith("BSD-3"): - return "BSD-3-Clause" - if normalized in {"BSD", "BSDLICENSE"}: + expected = "MIT" + elif normalized in {"ISC", "ISCLICENSE"}: + expected = "ISC" + elif normalized.startswith("APACHE-2") or normalized in {"APACHE2.0", "APACHELICENSE2.0"}: + expected = "Apache-2.0" + elif normalized.startswith("BSD-2"): + expected = "BSD-2-Clause" + elif normalized.startswith("BSD-3"): + expected = "BSD-3-Clause" + elif normalized in {"BSD", "BSDLICENSE"}: return _detect_bsd_license(license_text) - raise NoticeError(f"unknown or unsupported license: {declared}") - - _reject_prohibited(license_text) - upper_text = license_text.upper() - detected = [] - if "APACHE LICENSE" in upper_text and "VERSION 2.0" in upper_text: - detected.append("Apache-2.0") - if "PERMISSION IS HEREBY GRANTED, FREE OF CHARGE" in upper_text: - detected.append("MIT") - if "PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE" in upper_text: - detected.append("ISC") - if "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS" in upper_text: - detected.append(_detect_bsd_license(license_text)) + else: + raise NoticeError(f"unknown or unsupported license: {declared}") + if expected not in detected: + raise NoticeError(f"license text does not match declared license: {declared}") + return expected if not detected: raise NoticeError("license cannot be identified from dependency metadata or text") return " OR ".join(sorted(detected)) diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index d42c94b414..1485ff10b7 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -87,6 +87,10 @@ def test_unknown_license_fails_closed(self): with self.assertRaises(notices.NoticeError): notices.component_from_node_package(package, notices.ReadBudget()) + def test_declared_license_must_match_its_text(self): + with self.assertRaises(notices.NoticeError): + notices.normalize_license_id("MIT", APACHE_TEXT) + def test_bsd_license_is_classified_by_its_text(self): self.assertEqual(notices.normalize_license_id("BSD", BSD_2_TEXT), "BSD-2-Clause") self.assertEqual(notices.normalize_license_id("BSD", BSD_3_TEXT), "BSD-3-Clause") From c1f7d28f1efde036dc986fb0680a4108c35e6559 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:58:43 +0800 Subject: [PATCH 06/19] fix: isolate Go dependency scanning --- scripts/third_party_notices.py | 24 ++++++++++++++++-------- scripts/third_party_notices.test.py | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 20127083e0..5acc75427b 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -308,9 +308,9 @@ def _parse_json_stream(value: str) -> list[dict]: try: record, position = decoder.raw_decode(value, position) except json.JSONDecodeError as error: - raise NoticeError("invalid JSON from go list -m -json all") from error + raise NoticeError("invalid JSON from Go command") from error if not isinstance(record, dict): - raise NoticeError("invalid module record from go list -m -json all") + raise NoticeError("invalid module record from Go command") records.append(record) return records @@ -352,12 +352,20 @@ def collect_go_components(repo_root: Path, budget: ReadBudget) -> list[Component records = _go_runtime_module_records(repo_root) missing_directories = [record for record in records if not isinstance(record.get("Dir"), str)] if missing_directories: - try: - downloaded = subprocess.run( - ["go", "mod", "download", "-json", "all"], cwd=repo_root, capture_output=True, text=True, check=True - ) - except (OSError, subprocess.CalledProcessError) as error: - raise NoticeError("go mod download -json all failed while locating module source") from error + with tempfile.TemporaryDirectory(prefix="third-party-notices-go-download-") as temporary: + temp_root = Path(temporary) + _copy_input_file(repo_root, temp_root, "go.mod") + _copy_input_file(repo_root, temp_root, "go.sum") + try: + downloaded = subprocess.run( + ["go", "mod", "download", "-mod=mod", f"-modfile={temp_root / 'go.mod'}", "-json", "all"], + cwd=repo_root, + capture_output=True, + text=True, + check=True, + ) + except (OSError, subprocess.CalledProcessError) as error: + raise NoticeError("go mod download failed while locating module source") from error locations = { (record.get("Path"), record.get("Version")): record.get("Dir") for record in _parse_json_stream(downloaded.stdout) diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index 1485ff10b7..6f84bd3ab3 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -112,6 +112,25 @@ def test_runtime_module_collection_deduplicates_release_targets(self): self.assertEqual(run.call_count, len(notices.RELEASE_TARGETS)) self.assertEqual(run.call_args.kwargs["env"]["CGO_ENABLED"], "0") + def test_missing_module_directory_uses_an_isolated_modfile(self): + module = {"Path": "example.com/runtime", "Version": "v1.2.3"} + downloaded = dict(module, Dir="/tmp/runtime") + completed = mock.Mock(stdout=json.dumps(downloaded)) + component = notices.Component("runtime", "v1.2.3", "https://example.invalid", "MIT", "Copyright", "text") + + with tempfile.TemporaryDirectory() as directory: + repo = Path(directory) + (repo / "go.mod").write_text("module example.com/project\n", encoding="utf-8") + (repo / "go.sum").write_text("", encoding="utf-8") + with mock.patch.object(notices, "_go_runtime_module_records", return_value=[module]), \ + mock.patch.object(notices.subprocess, "run", return_value=completed) as run, \ + mock.patch.object(notices, "component_from_go_module", return_value=component): + self.assertEqual(notices.collect_go_components(repo, notices.ReadBudget()), [component]) + + command = run.call_args.args[0] + self.assertIn("-mod=mod", command) + self.assertTrue(any(argument.startswith("-modfile=") for argument in command)) + def test_license_hyphen_variant_is_accepted(self): with tempfile.TemporaryDirectory() as directory: package = make_package(Path(directory), "renamed-license-package", "MIT", MIT_TEXT) From 573f1af6abd39a3e02701c6e4ba81981db2ab1ef Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:00:26 +0800 Subject: [PATCH 07/19] fix: fail closed when Go sources are unavailable --- scripts/third_party_notices.py | 26 ++------------------------ scripts/third_party_notices.test.py | 19 ++++--------------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 5acc75427b..4da09d1a19 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -352,30 +352,8 @@ def collect_go_components(repo_root: Path, budget: ReadBudget) -> list[Component records = _go_runtime_module_records(repo_root) missing_directories = [record for record in records if not isinstance(record.get("Dir"), str)] if missing_directories: - with tempfile.TemporaryDirectory(prefix="third-party-notices-go-download-") as temporary: - temp_root = Path(temporary) - _copy_input_file(repo_root, temp_root, "go.mod") - _copy_input_file(repo_root, temp_root, "go.sum") - try: - downloaded = subprocess.run( - ["go", "mod", "download", "-mod=mod", f"-modfile={temp_root / 'go.mod'}", "-json", "all"], - cwd=repo_root, - capture_output=True, - text=True, - check=True, - ) - except (OSError, subprocess.CalledProcessError) as error: - raise NoticeError("go mod download failed while locating module source") from error - locations = { - (record.get("Path"), record.get("Version")): record.get("Dir") - for record in _parse_json_stream(downloaded.stdout) - if isinstance(record.get("Dir"), str) - } - for record in missing_directories: - directory = locations.get((record.get("Path"), record.get("Version"))) - if not isinstance(directory, str): - raise NoticeError(f"cannot locate module source for {record.get('Path')}@{record.get('Version')}") - record["Dir"] = directory + modules = ", ".join(f"{record.get('Path')}@{record.get('Version')}" for record in missing_directories) + raise NoticeError(f"go list did not locate module source: {modules}") return [component_from_go_module(record, budget) for record in records] diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index 6f84bd3ab3..cf0bc358de 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -112,24 +112,13 @@ def test_runtime_module_collection_deduplicates_release_targets(self): self.assertEqual(run.call_count, len(notices.RELEASE_TARGETS)) self.assertEqual(run.call_args.kwargs["env"]["CGO_ENABLED"], "0") - def test_missing_module_directory_uses_an_isolated_modfile(self): + def test_missing_module_directory_fails_closed(self): module = {"Path": "example.com/runtime", "Version": "v1.2.3"} - downloaded = dict(module, Dir="/tmp/runtime") - completed = mock.Mock(stdout=json.dumps(downloaded)) - component = notices.Component("runtime", "v1.2.3", "https://example.invalid", "MIT", "Copyright", "text") - with tempfile.TemporaryDirectory() as directory: repo = Path(directory) - (repo / "go.mod").write_text("module example.com/project\n", encoding="utf-8") - (repo / "go.sum").write_text("", encoding="utf-8") - with mock.patch.object(notices, "_go_runtime_module_records", return_value=[module]), \ - mock.patch.object(notices.subprocess, "run", return_value=completed) as run, \ - mock.patch.object(notices, "component_from_go_module", return_value=component): - self.assertEqual(notices.collect_go_components(repo, notices.ReadBudget()), [component]) - - command = run.call_args.args[0] - self.assertIn("-mod=mod", command) - self.assertTrue(any(argument.startswith("-modfile=") for argument in command)) + with mock.patch.object(notices, "_go_runtime_module_records", return_value=[module]): + with self.assertRaisesRegex(notices.NoticeError, "example.com/runtime@v1.2.3"): + notices.collect_go_components(repo, notices.ReadBudget()) def test_license_hyphen_variant_is_accepted(self): with tempfile.TemporaryDirectory() as directory: From e13418d30c392066a702bce52313889524fbc410 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:02:23 +0800 Subject: [PATCH 08/19] test: run third-party notice checks in CI --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 7c52173996..c9aa8d0a3e 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ fmt-check: fi script-test: + python3 scripts/third_party_notices.test.py bash scripts/resolve-changed-from.test.sh bash scripts/ci-workflow.test.sh bash scripts/pkg-pr-new-workflow.test.sh From 6888bc46f12be90e3e047b92db5d3f1b07224978 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:17:25 +0800 Subject: [PATCH 09/19] fix: tighten third-party notice validation --- .githooks/pre-commit | 17 ++- README.md | 2 +- THIRD_PARTY_NOTICES.md | 19 +--- .../third_party_notices/mit-package/LICENSE | 11 ++ scripts/third_party_notices.py | 82 +++++++++++--- scripts/third_party_notices.test.py | 101 ++++++++++++++++-- scripts/third_party_notices_hook.test.sh | 23 +++- 7 files changed, 214 insertions(+), 41 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 56b9ad824d..70def7487d 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -5,7 +5,22 @@ set -euo pipefail staged_files="$(git diff --cached --name-only --diff-filter=ACMR)" -if printf '%s\n' "$staged_files" | grep -Eq '^(go\.mod|go\.sum|package\.json|package-lock\.json)$' \ +go_dependency_change='^[+-][[:space:]]*(require|replace|exclude)[[:space:]]|^[+-][[:space:]]+[^[:space:]]+[[:space:]]+v[0-9]' +if git diff --cached --unified=0 -- go.mod | grep -Eq "$go_dependency_change"; then + dependency_change=1 +else + package_dependency_change="$(node -e ' +const dependencies = value => ({ + dependencies: value.dependencies ?? {}, + optionalDependencies: value.optionalDependencies ?? {}, +}); +const [before, after] = process.argv.slice(1).map(JSON.parse); +process.stdout.write(JSON.stringify(dependencies(before)) === JSON.stringify(dependencies(after)) ? "0" : "1"); +' "$(git show HEAD:package.json 2>/dev/null || printf '{}')" "$(git show :package.json 2>/dev/null || printf '{}')")" + dependency_change="$package_dependency_change" +fi + +if [ "$dependency_change" = 1 ] \ && ! printf '%s\n' "$staged_files" | grep -Fxq 'THIRD_PARTY_NOTICES.md'; then echo "Dependency metadata changed. Run 'make third-party-notices', review it, and stage THIRD_PARTY_NOTICES.md." >&2 exit 1 diff --git a/README.md b/README.md index b1e26ce199..9715899458 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,7 @@ Before opening a PR, see [AGENTS.md](./AGENTS.md) for the local build, test, and ### Third-party dependency notices -Install the repository hook once with `make install-git-hooks`. When adding, removing, or upgrading a Go or npm production dependency, run `make third-party-notices`, review the generated `THIRD_PARTY_NOTICES.md`, and commit it with the dependency change. CI verifies the file independently; the hook is only a local reminder. +Install the repository hook once with `make install-git-hooks`. When adding, removing, or upgrading a Go or npm production dependency, run `make third-party-notices`, review the generated `THIRD_PARTY_NOTICES.md`, and commit it with the dependency change. The local hook blocks a staged dependency declaration without the notice file; CI independently verifies that the generated file is current. ## License diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index a7c525934b..f15467848e 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -151,7 +151,6 @@ SOFTWARE. - Source: https://pkg.go.dev/github.com/atotto/clipboard@v0.1.4 - License: BSD-3-Clause - Copyright: Copyright (c) 2013 Ato Araki. All rights reserved. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -693,7 +692,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License: BSD-3-Clause - Copyright: Copyright (c) 2013, The GoGo Authors. All rights reserved. Copyright 2010 The Go Authors. All rights reserved. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -740,7 +738,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Source: https://pkg.go.dev/github.com/google/uuid@v1.6.0 - License: BSD-3-Clause - Copyright: Copyright (c) 2009,2014 Google Inc. All rights reserved. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -813,9 +810,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Version: v1.1.0 - Source: https://pkg.go.dev/github.com/inconshreveable/mousetrap@v1.1.0 - License: Apache-2.0 -- Copyright: copyright notice that is included in or attached to the work -copyright license to reproduce, prepare Derivative Works of, -Copyright 2022 Alan Shreve (@inconshreveable) +- Copyright: Copyright 2022 Alan Shreve (@inconshreveable) ### License Text ```text @@ -1493,8 +1488,7 @@ THE SOFTWARE. - Version: v1.10.2 - Source: https://pkg.go.dev/github.com/spf13/cobra@v1.10.2 - License: Apache-2.0 -- Copyright: copyright notice that is included in or attached to the work -copyright license to reproduce, prepare Derivative Works of, +- Copyright: Not specified ### License Text ```text @@ -1682,7 +1676,6 @@ copyright license to reproduce, prepare Derivative Works of, - License: BSD-3-Clause - Copyright: Copyright (c) 2012 Alex Ogier. All rights reserved. Copyright (c) 2012 The Go Authors. All rights reserved. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -1789,7 +1782,6 @@ SOFTWARE. - Source: https://pkg.go.dev/golang.org/x/image@v0.30.0 - License: BSD-3-Clause - Copyright: Copyright 2009 The Go Authors. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -1829,7 +1821,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Source: https://pkg.go.dev/golang.org/x/net@v0.33.0 - License: BSD-3-Clause - Copyright: Copyright 2009 The Go Authors. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -1869,7 +1860,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Source: https://pkg.go.dev/golang.org/x/sync@v0.16.0 - License: BSD-3-Clause - Copyright: Copyright 2009 The Go Authors. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -1909,7 +1899,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Source: https://pkg.go.dev/golang.org/x/sys@v0.33.0 - License: BSD-3-Clause - Copyright: Copyright 2009 The Go Authors. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -1949,7 +1938,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Source: https://pkg.go.dev/golang.org/x/term@v0.27.0 - License: BSD-3-Clause - Copyright: Copyright 2009 The Go Authors. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -1989,7 +1977,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Source: https://pkg.go.dev/golang.org/x/text@v0.28.0 - License: BSD-3-Clause - Copyright: Copyright 2009 The Go Authors. -copyright notice, this list of conditions and the following disclaimer ### License Text ```text @@ -2027,7 +2014,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Component: gopkg.in/yaml.v3 - Version: v3.0.1 - Source: https://pkg.go.dev/gopkg.in/yaml.v3@v3.0.1 -- License: Apache-2.0 OR MIT +- License: MIT - Copyright: copyright staring in 2011 when the project was ported over: Copyright (c) 2006-2010 Kirill Simonov Copyright (c) 2006-2011 Kirill Simonov diff --git a/scripts/testdata/third_party_notices/mit-package/LICENSE b/scripts/testdata/third_party_notices/mit-package/LICENSE index a84b21a389..79a9d44d00 100644 --- a/scripts/testdata/third_party_notices/mit-package/LICENSE +++ b/scripts/testdata/third_party_notices/mit-package/LICENSE @@ -7,3 +7,14 @@ of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software. + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 4da09d1a19..7a8fcbc8d5 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + """Generate a deterministic, fail-closed third-party notices document.""" from __future__ import annotations @@ -22,6 +25,41 @@ LICENSE_BASENAMES = ("LICENSE", "COPYING") NOTICE_BASENAMES = ("NOTICE",) PROHIBITED_LICENSE_WORDS = ("GPL", "LGPL", "AGPL", "SSPL", "GENERAL PUBLIC LICENSE", "SERVER SIDE PUBLIC") +MIT_REQUIRED_CLAUSES = ( + "permission is hereby granted, free of charge", + "the above copyright notice and this permission notice shall be included in all copies or substantial portions of the software", + "the software is provided as is", + "in no event shall the authors or copyright holders be liable for any claim", +) +APACHE_REQUIRED_CLAUSES = ( + "apache license", + "version 2.0", + "terms and conditions for use, reproduction, and distribution", + "grant of copyright license", + "grant of patent license", + "redistribution", + "submission of contributions", + "trademarks", + "disclaimer of warranty", + "limitation of liability", + "accepting warranty or additional liability", +) +ISC_REQUIRED_CLAUSES = ( + "permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted", + "the above copyright notice and this permission notice shall be included in all copies", + "the author disclaims all warranties with regard to this software", + "in no event shall the author be liable for any special, direct, indirect, or consequential damages", +) +BSD_REQUIRED_CLAUSES = ( + "redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met", + "redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer", + "redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer", + "this software is provided by the copyright holders and contributors as is", +) +BSD_LIABILITY_CLAUSES = ( + "in no event shall the copyright holder or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages", + "in no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages", +) RELEASE_TARGETS = ( ("darwin", "amd64"), ("darwin", "arm64"), @@ -174,28 +212,40 @@ def _reject_prohibited(value: str) -> None: raise NoticeError(f"prohibited license: {value}") +def _normalized_license_text(value: str) -> str: + return re.sub(r"[\"“”]", "", re.sub(r"\s+", " ", value)).casefold() + + +def _has_required_clauses(license_text: str, clauses: Iterable[str]) -> bool: + normalized = _normalized_license_text(license_text) + return all(clause in normalized for clause in clauses) + + def _detect_bsd_license(license_text: str) -> str: - upper_text = license_text.upper() - if "ALL ADVERTISING MATERIALS" in upper_text: + normalized = _normalized_license_text(license_text) + if "all advertising materials" in normalized: raise NoticeError("unsupported BSD-4-Clause license") - if "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS" not in upper_text: - raise NoticeError("license cannot be identified as BSD-2-Clause or BSD-3-Clause") - if "NEITHER THE NAME OF" in upper_text: + if ( + not _has_required_clauses(license_text, BSD_REQUIRED_CLAUSES) + or not any(clause in normalized for clause in BSD_LIABILITY_CLAUSES) + ): + raise NoticeError("incomplete BSD-2-Clause or BSD-3-Clause license") + if "neither the name of" in normalized: return "BSD-3-Clause" return "BSD-2-Clause" def _detect_license_ids(license_text: str) -> set[str]: _reject_prohibited(license_text) - upper_text = license_text.upper() + normalized = _normalized_license_text(license_text) detected = set() - if "APACHE LICENSE" in upper_text and "VERSION 2.0" in upper_text: + if _has_required_clauses(license_text, APACHE_REQUIRED_CLAUSES): detected.add("Apache-2.0") - if "PERMISSION IS HEREBY GRANTED, FREE OF CHARGE" in upper_text: + if _has_required_clauses(license_text, MIT_REQUIRED_CLAUSES): detected.add("MIT") - if "PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE" in upper_text: + if _has_required_clauses(license_text, ISC_REQUIRED_CLAUSES): detected.add("ISC") - if "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS" in upper_text: + if "redistribution and use in source and binary forms" in normalized: detected.add(_detect_bsd_license(license_text)) return detected @@ -236,11 +286,13 @@ def normalize_license_id(value: object, license_text: str) -> str: def _copyright_lines(*texts: str) -> str: lines = [] for text in texts: - lines.extend( - line.strip() - for line in text.splitlines() - if line.lstrip().lower().startswith("copyright") or line.lstrip().startswith("©") - ) + for line in text.splitlines(): + stripped = line.strip() + normalized = stripped.lower() + if normalized.startswith(("copyright notice", "copyright license")): + continue + if normalized.startswith("copyright") or stripped.startswith("©"): + lines.append(stripped) return "\n".join(dict.fromkeys(lines)) or "Not specified" diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index cf0bc358de..33d1dda7c5 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + """Behavior tests for the third-party notice generator.""" import importlib.util @@ -25,24 +28,90 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal -in the Software without restriction. +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. """ APACHE_TEXT = """Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Grant of Copyright License. +2. Grant of Patent License. +4. Redistribution. +5. Submission of Contributions. +6. Trademarks. +7. Disclaimer of Warranty. +8. Limitation of Liability. +9. Accepting Warranty or Additional Liability. """ BSD_2_TEXT = """BSD 2-Clause License Copyright (c) 2024 Example Authors -Redistribution and use in source and binary forms, with or without modification, are permitted. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ BSD_3_TEXT = """BSD 3-Clause License Copyright (c) 2024 Example Authors -Redistribution and use in source and binary forms, with or without modification, are permitted. -Neither the name of Example Authors nor the names of its contributors may be used to endorse products. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of Example Authors nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ @@ -95,6 +164,27 @@ def test_bsd_license_is_classified_by_its_text(self): self.assertEqual(notices.normalize_license_id("BSD", BSD_2_TEXT), "BSD-2-Clause") self.assertEqual(notices.normalize_license_id("BSD", BSD_3_TEXT), "BSD-3-Clause") + def test_truncated_approved_license_text_fails_closed(self): + cases = ( + ("MIT", "Permission is hereby granted, free of charge"), + ("Apache-2.0", "Apache License\nVersion 2.0"), + ("BSD-2-Clause", "Redistribution and use in source and binary forms"), + ("ISC", "Permission to use, copy, modify, and/or distribute"), + ) + + for declared, text in cases: + with self.subTest(declared=declared): + with self.assertRaises(notices.NoticeError): + notices.normalize_license_id(declared, text) + + def test_copyright_clauses_are_not_rendered_as_attribution(self): + self.assertEqual( + notices._copyright_lines( + "Copyright 2024 Example\nCopyright notice, this list of conditions must be retained\n© 2025 Another" + ), + "Copyright 2024 Example\n© 2025 Another", + ) + def test_runtime_module_collection_deduplicates_release_targets(self): module = {"Path": "example.com/runtime", "Version": "v1.2.3", "Dir": "/tmp/runtime"} main = {"Path": "github.com/larksuite/cli", "Main": True} @@ -163,8 +253,7 @@ def test_total_read_limit_is_shared_between_components(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) budget = notices.ReadBudget() - text = ("Copyright (c) Example\nPermission is hereby granted, free of charge, to any person " - "obtaining a copy\n" + "x" * (notices.MAX_FILE_BYTES - 200_000)) + text = MIT_TEXT + "x" * (notices.MAX_FILE_BYTES - 200_000) packages = [make_package(root, f"package-{index}", "MIT", text) for index in range(9)] for package in packages[:8]: diff --git a/scripts/third_party_notices_hook.test.sh b/scripts/third_party_notices_hook.test.sh index 99e8d25ceb..674526ea3f 100755 --- a/scripts/third_party_notices_hook.test.sh +++ b/scripts/third_party_notices_hook.test.sh @@ -15,9 +15,9 @@ chmod +x "$TMP_DIR/.githooks/pre-commit" git -C "$TMP_DIR" init --quiet git -C "$TMP_DIR" config core.hooksPath .githooks git -C "$TMP_DIR" config user.name "Third-Party Notices Test" -git -C "$TMP_DIR" config user.email "third-party-notices@example.invalid" +git -C "$TMP_DIR" config user.email "notices-test@example.invalid" -printf 'module example.com/test\n' > "$TMP_DIR/go.mod" +printf 'module example.com/test\n\nrequire example.com/dependency v1.2.3\n' > "$TMP_DIR/go.mod" git -C "$TMP_DIR" add go.mod if git -C "$TMP_DIR" commit --quiet -m "test: missing notices"; then echo "pre-commit should block staged dependency metadata without notices" >&2 @@ -28,6 +28,25 @@ printf '# Third-Party Notices\n' > "$TMP_DIR/THIRD_PARTY_NOTICES.md" git -C "$TMP_DIR" add THIRD_PARTY_NOTICES.md git -C "$TMP_DIR" commit --quiet -m "test: include notices" +printf 'go 1.25.0\n' >> "$TMP_DIR/go.mod" +git -C "$TMP_DIR" add go.mod +git -C "$TMP_DIR" commit --quiet -m "test: allow non-dependency metadata" + +printf '{"dependencies":{"example":"1.0.0"}}\n' > "$TMP_DIR/package.json" +git -C "$TMP_DIR" add package.json +if git -C "$TMP_DIR" commit --quiet -m "test: missing npm notices"; then + echo "pre-commit should block staged npm dependency declarations without notices" >&2 + exit 1 +fi + +printf '# Third-Party Notices\n\nUpdated for npm dependency.\n' > "$TMP_DIR/THIRD_PARTY_NOTICES.md" +git -C "$TMP_DIR" add THIRD_PARTY_NOTICES.md +git -C "$TMP_DIR" commit --quiet -m "test: include npm notices" + +printf '{"description":"metadata only","dependencies":{"example":"1.0.0"}}\n' > "$TMP_DIR/package.json" +git -C "$TMP_DIR" add package.json +git -C "$TMP_DIR" commit --quiet -m "test: allow npm metadata" + if rg -q '^[[:space:]]*(make|python3?|git[[:space:]]+add)\b' "$ROOT_DIR/.githooks/pre-commit"; then echo "pre-commit must only inspect the Git index; it must not execute or stage worktree code" >&2 exit 1 From 53651d4ac1a64424f700014b0f155fef9e2606e7 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:19:54 +0800 Subject: [PATCH 10/19] test: simplify notice test imports --- scripts/third_party_notices.test.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index 33d1dda7c5..02f21c4b69 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -9,9 +9,8 @@ import shutil import sys import tempfile -import unittest from pathlib import Path -from unittest import mock +from unittest import TestCase, main, mock SCRIPT = Path(__file__).with_name("third_party_notices.py") @@ -126,7 +125,7 @@ def make_package(root: Path, name: str, license_name: str, license_text: str) -> return package -class ThirdPartyNoticesTests(unittest.TestCase): +class ThirdPartyNoticesTests(TestCase): def test_render_sorts_components_stably(self): components = [ notices.Component("zeta", "2.0.0", "https://z", "MIT", "Copyright Z", "z text"), @@ -294,4 +293,4 @@ def test_check_compares_without_mutating_the_output(self): if __name__ == "__main__": - unittest.main() + main() From e2cfc8c6ecf9f3554b5407bb96395102f7faa1ad Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:09:02 +0800 Subject: [PATCH 11/19] fix: scan Node dependencies for release targets --- scripts/third_party_notices.py | 47 +++++++++++++++++++++++------ scripts/third_party_notices.test.py | 31 +++++++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 7a8fcbc8d5..241c54b36b 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -69,6 +69,11 @@ ("windows", "amd64"), ("windows", "arm64"), ) +NODE_OS_ALIASES = {"windows": "win32"} +NODE_CPU_ALIASES = {"amd64": "x64"} +NODE_RELEASE_TARGETS = tuple( + (NODE_OS_ALIASES.get(goos, goos), NODE_CPU_ALIASES.get(goarch, goarch)) for goos, goarch in RELEASE_TARGETS +) class NoticeError(RuntimeError): @@ -445,15 +450,39 @@ def _node_package_directories(node_modules: Path) -> Iterable[Path]: def collect_node_components(repo_root: Path, budget: ReadBudget) -> list[Component]: with tempfile.TemporaryDirectory(prefix="third-party-notices-") as temporary: temp_root = Path(temporary) - _copy_input_file(repo_root, temp_root, "package.json") - _copy_input_file(repo_root, temp_root, "package-lock.json") - try: - subprocess.run( - ["npm", "ci", "--ignore-scripts", "--omit=dev"], cwd=temp_root, capture_output=True, text=True, check=True - ) - except (OSError, subprocess.CalledProcessError) as error: - raise NoticeError("npm ci --ignore-scripts --omit=dev failed") from error - return [component_from_node_package(directory, budget) for directory in _node_package_directories(temp_root / "node_modules")] + components: dict[tuple[str, str, str], Component] = {} + for node_os, node_cpu in NODE_RELEASE_TARGETS: + target_root = temp_root / f"{node_os}-{node_cpu}" + target_root.mkdir() + _copy_input_file(repo_root, target_root, "package.json") + _copy_input_file(repo_root, target_root, "package-lock.json") + try: + subprocess.run( + [ + "npm", + "ci", + "--ignore-scripts", + "--omit=dev", + f"--os={node_os}", + f"--cpu={node_cpu}", + ], + cwd=target_root, + capture_output=True, + text=True, + check=True, + ) + except (OSError, subprocess.CalledProcessError) as error: + raise NoticeError(f"npm ci failed for {node_os}/{node_cpu}") from error + for directory in _node_package_directories(target_root / "node_modules"): + component = component_from_node_package(directory, budget) + key = (component.name, component.version, component.source) + existing = components.get(key) + if existing and existing != component: + raise NoticeError( + f"npm dependency metadata differs across release targets: {component.name}@{component.version}" + ) + components[key] = component + return [components[key] for key in sorted(components)] def render_notices(components: Iterable[Component]) -> str: diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index 02f21c4b69..cf6d1d9c89 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -201,6 +201,37 @@ def test_runtime_module_collection_deduplicates_release_targets(self): self.assertEqual(run.call_count, len(notices.RELEASE_TARGETS)) self.assertEqual(run.call_args.kwargs["env"]["CGO_ENABLED"], "0") + def test_node_component_collection_scans_and_deduplicates_release_targets(self): + common = notices.Component("common", "1.0.0", "https://common.example", "MIT", "Copyright", "text") + darwin_only = notices.Component("darwin-only", "1.0.0", "https://darwin.example", "MIT", "Copyright", "text") + + def package_directories(node_modules: Path): + target = node_modules.parent.name + yield node_modules / "common" + if target == "darwin-arm64": + yield node_modules / "darwin-only" + + def component_from_package(directory: Path, budget: notices.ReadBudget): + if directory.name == "darwin-only": + return darwin_only + return common + + with tempfile.TemporaryDirectory() as directory: + repo = Path(directory) + (repo / "package.json").write_text('{"name":"example"}', encoding="utf-8") + (repo / "package-lock.json").write_text('{"lockfileVersion":3}', encoding="utf-8") + with mock.patch.object(notices.subprocess, "run", return_value=mock.Mock()) as run, \ + mock.patch.object(notices, "_node_package_directories", side_effect=package_directories), \ + mock.patch.object(notices, "component_from_node_package", side_effect=component_from_package): + components = notices.collect_node_components(repo, notices.ReadBudget()) + + self.assertEqual(components, [common, darwin_only]) + self.assertEqual(run.call_count, len(notices.NODE_RELEASE_TARGETS)) + self.assertEqual( + {tuple(call.args[0][-2:]) for call in run.call_args_list}, + {(f"--os={node_os}", f"--cpu={node_cpu}") for node_os, node_cpu in notices.NODE_RELEASE_TARGETS}, + ) + def test_missing_module_directory_fails_closed(self): module = {"Path": "example.com/runtime", "Version": "v1.2.3"} with tempfile.TemporaryDirectory() as directory: From e2fd38478b12e77f032a5ec0fc62701cf7bb175c Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:28:36 +0800 Subject: [PATCH 12/19] refactor: simplify third-party notice checks --- .../third_party_notices/mit-package/LICENSE | 20 ---- .../mit-package/package.json | 1 - scripts/third_party_notices.py | 42 +++---- scripts/third_party_notices.test.py | 112 +++--------------- 4 files changed, 36 insertions(+), 139 deletions(-) delete mode 100644 scripts/testdata/third_party_notices/mit-package/LICENSE delete mode 100644 scripts/testdata/third_party_notices/mit-package/package.json diff --git a/scripts/testdata/third_party_notices/mit-package/LICENSE b/scripts/testdata/third_party_notices/mit-package/LICENSE deleted file mode 100644 index 79a9d44d00..0000000000 --- a/scripts/testdata/third_party_notices/mit-package/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -MIT License - -Copyright (c) 2024 Fixture Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software. - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/scripts/testdata/third_party_notices/mit-package/package.json b/scripts/testdata/third_party_notices/mit-package/package.json deleted file mode 100644 index 782391db01..0000000000 --- a/scripts/testdata/third_party_notices/mit-package/package.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"fixture-mit","version":"1.2.3","license":"MIT","repository":"https://example.invalid/fixture-mit"} diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 241c54b36b..bc75f66260 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -315,41 +315,43 @@ def _repository_source(metadata: dict, fallback: str) -> str: return source -def component_from_node_package(package_dir: Path, budget: ReadBudget) -> Component: - metadata = _read_json(package_dir, package_dir / "package.json", budget) - name, version = metadata.get("name"), metadata.get("version") - if not isinstance(name, str) or not name or not isinstance(version, str) or not version: - raise NoticeError(f"missing name or version in dependency metadata: {package_dir}") +def _component_from_package( + package_dir: Path, budget: ReadBudget, *, name: str, version: str, source: str, declared_license: object +) -> Component: license_text = _document_text(package_dir, LICENSE_BASENAMES, budget, required=True) notice_text = _document_text(package_dir, NOTICE_BASENAMES, budget, required=False) - license_id = normalize_license_id(metadata.get("license"), license_text) return Component( name=name, version=version, - source=_repository_source(metadata, f"https://www.npmjs.com/package/{name}/v/{version}"), - license_id=license_id, + source=source, + license_id=normalize_license_id(declared_license, license_text), copyright=_copyright_lines(license_text, notice_text), license_text=license_text, notice_text=notice_text, ) +def component_from_node_package(package_dir: Path, budget: ReadBudget) -> Component: + metadata = _read_json(package_dir, package_dir / "package.json", budget) + name, version = metadata.get("name"), metadata.get("version") + if not isinstance(name, str) or not name or not isinstance(version, str) or not version: + raise NoticeError(f"missing name or version in dependency metadata: {package_dir}") + return _component_from_package( + package_dir, + budget, + name=name, + version=version, + source=_repository_source(metadata, f"https://www.npmjs.com/package/{name}/v/{version}"), + declared_license=metadata.get("license"), + ) + + def component_from_go_module(module: dict, budget: ReadBudget) -> Component: name, version, directory = module.get("Path"), module.get("Version"), module.get("Dir") if not isinstance(name, str) or not isinstance(version, str) or not isinstance(directory, str): raise NoticeError("go list returned a module with missing path, version, or directory") - module_dir = Path(directory) - license_text = _document_text(module_dir, LICENSE_BASENAMES, budget, required=True) - notice_text = _document_text(module_dir, NOTICE_BASENAMES, budget, required=False) - license_id = normalize_license_id(None, license_text) - return Component( - name=name, - version=version, - source=f"https://pkg.go.dev/{name}@{version}", - license_id=license_id, - copyright=_copyright_lines(license_text, notice_text), - license_text=license_text, - notice_text=notice_text, + return _component_from_package( + Path(directory), budget, name=name, version=version, source=f"https://pkg.go.dev/{name}@{version}", declared_license=None ) diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index cf6d1d9c89..469d3cd4ec 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -6,7 +6,6 @@ import importlib.util import json -import shutil import sys import tempfile from pathlib import Path @@ -20,98 +19,23 @@ sys.modules[SPEC.name] = notices SPEC.loader.exec_module(notices) -FIXTURES = Path(__file__).parent / "testdata" / "third_party_notices" -MIT_TEXT = """MIT License - -Copyright (c) 2024 Example Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the \"Software\"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT_TEXT = """Permission is hereby granted, free of charge. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED AS IS. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM. """ -APACHE_TEXT = """Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Grant of Copyright License. -2. Grant of Patent License. -4. Redistribution. -5. Submission of Contributions. -6. Trademarks. -7. Disclaimer of Warranty. -8. Limitation of Liability. -9. Accepting Warranty or Additional Liability. +APACHE_TEXT = """Apache License Version 2.0 +Terms and Conditions for Use, Reproduction, and Distribution +Grant of Copyright License; Grant of Patent License; Redistribution; Submission of Contributions; Trademarks; Disclaimer of Warranty; Limitation of Liability; Accepting Warranty or Additional Liability. """ -BSD_2_TEXT = """BSD 2-Clause License - -Copyright (c) 2024 Example Authors - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" -BSD_3_TEXT = """BSD 3-Clause License - -Copyright (c) 2024 Example Authors - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of Example Authors nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +BSD_TEXT = """Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met. +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer. +This software is provided by the copyright holders and contributors as is. +In no event shall the copyright holder or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages. """ +BSD_2_TEXT = BSD_TEXT +BSD_3_TEXT = BSD_TEXT + "Neither the name of Example Authors nor its contributors may be used to endorse products.\n" def make_package(root: Path, name: str, license_name: str, license_text: str) -> Path: @@ -299,14 +223,6 @@ def test_invalid_utf8_license_is_rejected(self): with self.assertRaises(notices.NoticeError): notices.component_from_node_package(package, notices.ReadBudget()) - def test_fixture_is_safe_to_parse(self): - # A real, checked-in fixture catches accidental fixture path regressions. - with tempfile.TemporaryDirectory() as directory: - package = Path(directory) / "fixture" - shutil.copytree(FIXTURES / "mit-package", package) - component = notices.component_from_node_package(package, notices.ReadBudget()) - self.assertEqual((component.name, component.version, component.license_id), ("fixture-mit", "1.2.3", "MIT")) - def test_check_compares_without_mutating_the_output(self): component = notices.Component("example", "1.0.0", "https://example.invalid", "MIT", "Copyright", "text") with tempfile.TemporaryDirectory() as directory: From bc44818ce260796f6ce6ba00e302340c9eec64c5 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:58:16 +0800 Subject: [PATCH 13/19] refactor: avoid duplicate preview notice checks --- .github/workflows/pkg-pr-new.yml | 15 ++------------- Makefile | 1 - scripts/pkg-pr-new-workflow.test.sh | 23 ----------------------- 3 files changed, 2 insertions(+), 37 deletions(-) delete mode 100755 scripts/pkg-pr-new-workflow.test.sh diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index 398f88fe8d..a10b7674ca 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -21,20 +21,9 @@ jobs: with: go-version-file: go.mod - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 with: - python-version: '3.x' - - - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 - with: - node-version: '22.14.0' - package-manager-cache: false - - - name: Install pinned npm - run: npm install --global npm@11.16.0 - - - name: Check third-party notices - run: make check-third-party-notices + node-version: lts/* - name: Build preview package run: ./scripts/build-pkg-pr-new.sh diff --git a/Makefile b/Makefile index c9aa8d0a3e..dd78a1e767 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,6 @@ script-test: python3 scripts/third_party_notices.test.py bash scripts/resolve-changed-from.test.sh bash scripts/ci-workflow.test.sh - bash scripts/pkg-pr-new-workflow.test.sh bash scripts/release-workflow.test.sh bash scripts/semantic-review-workflow.test.sh bash scripts/third_party_notices_hook.test.sh diff --git a/scripts/pkg-pr-new-workflow.test.sh b/scripts/pkg-pr-new-workflow.test.sh deleted file mode 100755 index 855c8fd26d..0000000000 --- a/scripts/pkg-pr-new-workflow.test.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2026 Lark Technologies Pte. Ltd. -# SPDX-License-Identifier: MIT - -set -euo pipefail - -ruby -ryaml <<'RUBY' -workflow = YAML.load_file(".github/workflows/pkg-pr-new.yml") -abort("pkg.pr.new workflow must have read-only contents permission") unless workflow.fetch("permissions") == { "contents" => "read" } - -job = workflow.fetch("jobs").fetch("publish") -abort("pkg.pr.new workflow must time out") unless job.fetch("timeout-minutes") == 15 -abort("pkg.pr.new workflow must not reference secrets") if job.to_s.include?("secrets.") - -steps = job.fetch("steps") -notices = steps.index { |step| step["name"] == "Check third-party notices" } -build = steps.index { |step| step["name"] == "Build preview package" } -publish = steps.index { |step| step["name"] == "Publish to pkg.pr.new" } -abort("pkg.pr.new must check notices before building") unless notices && build && notices < build -abort("pkg.pr.new must check notices before publishing") unless notices < publish -abort("pkg.pr.new must use Node 22.14.0") unless steps.any? { |step| step.dig("with", "node-version") == "22.14.0" } -abort("pkg.pr.new must pin npm 11.16.0") unless steps.any? { |step| step["run"] == "npm install --global npm@11.16.0" } -RUBY From 0ce1e44feda27e8a15f851a928f61228bc71fd09 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:29:30 +0800 Subject: [PATCH 14/19] fix: clarify license review guidance --- .github/workflows/release.yml | 2 +- README.md | 2 +- scripts/third_party_notices.py | 14 ++++++++++---- scripts/third_party_notices.test.py | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ddf80b9235..d3af1eff67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -604,7 +604,7 @@ jobs: 'Select the recovery action from the failed-step diagnosis. If a Draft or public Release already exists, use **Re-run failed jobs**, not **Re-run all jobs**:' \ '' \ '- **preflight:** network or fetch failure → retry preflight. Version/tag validation failure → correct it, then create a new tag.' \ - '- **license-compliance:** dependency registry failure → retry this job. Missing, unknown, or disallowed license → update the dependency or notices, then create a new tag.' \ + '- **license-compliance:** dependency registry failure → retry this job. Missing, unidentifiable, or currently unsupported license → update the dependency/notices or complete manual review, then create a new tag.' \ '- **build-sign-notarize:** transient build/service failure → retry build. Code or release configuration issue → correct it, then create a new tag.' \ '- **create-draft-release:** GitHub Draft Release API/upload failure → retry draft; a partial Draft is verified and only missing assets are uploaded. Release-candidate inconsistency → delete the Draft, then retry build.' \ '- **verify-macos:** runner or network failure → retry only the failed matrix child. Checksum, signing, notarization, or runtime failure → delete the Draft, then retry build.' \ diff --git a/README.md b/README.md index 9715899458..a9288b7905 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,7 @@ Before opening a PR, see [AGENTS.md](./AGENTS.md) for the local build, test, and ### Third-party dependency notices -Install the repository hook once with `make install-git-hooks`. When adding, removing, or upgrading a Go or npm production dependency, run `make third-party-notices`, review the generated `THIRD_PARTY_NOTICES.md`, and commit it with the dependency change. The local hook blocks a staged dependency declaration without the notice file; CI independently verifies that the generated file is current. +Install the repository hook once with `make install-git-hooks`. When adding, removing, or upgrading a Go or npm production dependency, run `make third-party-notices`, review the generated `THIRD_PARTY_NOTICES.md`, and commit it with the dependency change. The local hook blocks a staged dependency declaration without the notice file; CI independently verifies that the generated file is current. The automated verifier currently covers MIT, BSD-2-Clause, BSD-3-Clause, ISC, and Apache-2.0; another or unidentifiable license is blocked for manual review, rather than treated as a company-wide prohibition. ## License diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index bc75f66260..916a968ce9 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -2,7 +2,7 @@ # Copyright (c) 2026 Lark Technologies Pte. Ltd. # SPDX-License-Identifier: MIT -"""Generate a deterministic, fail-closed third-party notices document.""" +"""Generate notices and verify the licenses covered by current automation.""" from __future__ import annotations @@ -256,7 +256,7 @@ def _detect_license_ids(license_text: str) -> set[str]: def normalize_license_id(value: object, license_text: str) -> str: - """Return an allowed SPDX-like identifier, or fail closed.""" + """Return a currently auto-verified SPDX-like identifier, or fail closed.""" declared = "" if isinstance(value, str): declared = value.strip() @@ -279,12 +279,18 @@ def normalize_license_id(value: object, license_text: str) -> str: elif normalized in {"BSD", "BSDLICENSE"}: return _detect_bsd_license(license_text) else: - raise NoticeError(f"unknown or unsupported license: {declared}") + raise NoticeError( + f"license requires manual review; " + f"it is outside the current automated verification set: {declared}" + ) if expected not in detected: raise NoticeError(f"license text does not match declared license: {declared}") return expected if not detected: - raise NoticeError("license cannot be identified from dependency metadata or text") + raise NoticeError( + "license requires manual review; " + "it cannot be identified from dependency metadata or text" + ) return " OR ".join(sorted(detected)) diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index 469d3cd4ec..cca400cc5a 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -72,7 +72,7 @@ def test_apache_notice_is_preserved_in_component(self): self.assertEqual(component.license_id, "Apache-2.0") self.assertEqual(component.notice_text, "Example Apache NOTICE\n") - def test_unknown_license_fails_closed(self): + def test_license_outside_automated_set_fails_closed(self): with tempfile.TemporaryDirectory() as directory: package = make_package(Path(directory), "unknown-package", "Proprietary", MIT_TEXT) From 141d09ad319c978e9f584915584dddfbb4c43b06 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:39:29 +0800 Subject: [PATCH 15/19] fix: match complete license templates --- scripts/third_party_notices.py | 151 +++++++++++++++++++--------- scripts/third_party_notices.test.py | 42 ++++---- 2 files changed, 127 insertions(+), 66 deletions(-) diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 916a968ce9..3cccbd50fa 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -25,41 +25,81 @@ LICENSE_BASENAMES = ("LICENSE", "COPYING") NOTICE_BASENAMES = ("NOTICE",) PROHIBITED_LICENSE_WORDS = ("GPL", "LGPL", "AGPL", "SSPL", "GENERAL PUBLIC LICENSE", "SERVER SIDE PUBLIC") -MIT_REQUIRED_CLAUSES = ( - "permission is hereby granted, free of charge", - "the above copyright notice and this permission notice shall be included in all copies or substantial portions of the software", - "the software is provided as is", - "in no event shall the authors or copyright holders be liable for any claim", -) -APACHE_REQUIRED_CLAUSES = ( - "apache license", - "version 2.0", - "terms and conditions for use, reproduction, and distribution", - "grant of copyright license", - "grant of patent license", - "redistribution", - "submission of contributions", - "trademarks", - "disclaimer of warranty", - "limitation of liability", - "accepting warranty or additional liability", -) -ISC_REQUIRED_CLAUSES = ( - "permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted", - "the above copyright notice and this permission notice shall be included in all copies", - "the author disclaims all warranties with regard to this software", - "in no event shall the author be liable for any special, direct, indirect, or consequential damages", -) -BSD_REQUIRED_CLAUSES = ( - "redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met", - "redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer", - "redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer", - "this software is provided by the copyright holders and contributors as is", -) -BSD_LIABILITY_CLAUSES = ( - "in no event shall the copyright holder or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages", - "in no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages", -) + +# SPDX license texts, with copyright headers and the non-operative Apache appendix +# intentionally omitted. Matching requires the complete operative text in order; +# whitespace and quotation-mark variants are normalized below. +LICENSE_TEMPLATES = { + "MIT": ( + """Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the \"Software\"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. +THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE.""", + ), + "ISC": ( + """Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +THE SOFTWARE IS PROVIDED \"AS IS\" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.""", + """Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice are included in all copies. +THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.""", + ), + "BSD-2-Clause": ( + """Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT <> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.""", + ), + "BSD-3-Clause": ( + """Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of <> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT <> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.""", + ), + "Apache-2.0": ( + """TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +1. Definitions. +\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. +\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License. +\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. +\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). +\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. +\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\" +\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. +3. Grant of Patent License. +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. +If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and +(b) You must cause any modified files to carry prominent notices stating that You changed the files; and +(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +(d) If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. +The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. +You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. +8. Limitation of Liability. +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, 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 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.""", + ), +} RELEASE_TARGETS = ( ("darwin", "amd64"), ("darwin", "arm64"), @@ -218,37 +258,46 @@ def _reject_prohibited(value: str) -> None: def _normalized_license_text(value: str) -> str: - return re.sub(r"[\"“”]", "", re.sub(r"\s+", " ", value)).casefold() + without_list_markers = re.sub(r"(?m)^\s*(?:\d+\.|\*)\s+", "", value) + return re.sub(r"[\"“”]", "", re.sub(r"\s+", " ", without_list_markers)).casefold() -def _has_required_clauses(license_text: str, clauses: Iterable[str]) -> bool: +def _matches_standard_template(license_id: str, license_text: str) -> bool: + """Require a complete SPDX template, not merely a collection of headings.""" normalized = _normalized_license_text(license_text) - return all(clause in normalized for clause in clauses) + for template in LICENSE_TEMPLATES[license_id]: + normalized_template = _normalized_license_text(template) + if license_id.startswith("BSD-"): + pattern = re.escape(normalized_template) + pattern = pattern.replace(re.escape("<>"), r".+?") + pattern = pattern.replace(re.escape("<>"), r"(?:holder|owner)") + if re.search(pattern, normalized): + return True + elif normalized_template in normalized: + return True + return False def _detect_bsd_license(license_text: str) -> str: normalized = _normalized_license_text(license_text) if "all advertising materials" in normalized: raise NoticeError("unsupported BSD-4-Clause license") - if ( - not _has_required_clauses(license_text, BSD_REQUIRED_CLAUSES) - or not any(clause in normalized for clause in BSD_LIABILITY_CLAUSES) - ): - raise NoticeError("incomplete BSD-2-Clause or BSD-3-Clause license") - if "neither the name of" in normalized: + if _matches_standard_template("BSD-3-Clause", license_text): return "BSD-3-Clause" - return "BSD-2-Clause" + if _matches_standard_template("BSD-2-Clause", license_text): + return "BSD-2-Clause" + raise NoticeError("incomplete BSD-2-Clause or BSD-3-Clause license") def _detect_license_ids(license_text: str) -> set[str]: _reject_prohibited(license_text) normalized = _normalized_license_text(license_text) detected = set() - if _has_required_clauses(license_text, APACHE_REQUIRED_CLAUSES): + if _matches_standard_template("Apache-2.0", license_text): detected.add("Apache-2.0") - if _has_required_clauses(license_text, MIT_REQUIRED_CLAUSES): + if _matches_standard_template("MIT", license_text): detected.add("MIT") - if _has_required_clauses(license_text, ISC_REQUIRED_CLAUSES): + if _matches_standard_template("ISC", license_text): detected.add("ISC") if "redistribution and use in source and binary forms" in normalized: detected.add(_detect_bsd_license(license_text)) @@ -326,11 +375,15 @@ def _component_from_package( ) -> Component: license_text = _document_text(package_dir, LICENSE_BASENAMES, budget, required=True) notice_text = _document_text(package_dir, NOTICE_BASENAMES, budget, required=False) + try: + license_id = normalize_license_id(declared_license, license_text) + except NoticeError as error: + raise NoticeError(f"{name}@{version}: {error}") from error return Component( name=name, version=version, source=source, - license_id=normalize_license_id(declared_license, license_text), + license_id=license_id, copyright=_copyright_lines(license_text, notice_text), license_text=license_text, notice_text=notice_text, diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index cca400cc5a..c6385d81fa 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -19,23 +19,14 @@ sys.modules[SPEC.name] = notices SPEC.loader.exec_module(notices) -MIT_TEXT = """Permission is hereby granted, free of charge. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED AS IS. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM. -""" -APACHE_TEXT = """Apache License Version 2.0 -Terms and Conditions for Use, Reproduction, and Distribution -Grant of Copyright License; Grant of Patent License; Redistribution; Submission of Contributions; Trademarks; Disclaimer of Warranty; Limitation of Liability; Accepting Warranty or Additional Liability. -""" -BSD_TEXT = """Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met. -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer. -This software is provided by the copyright holders and contributors as is. -In no event shall the copyright holder or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages. -""" -BSD_2_TEXT = BSD_TEXT -BSD_3_TEXT = BSD_TEXT + "Neither the name of Example Authors nor its contributors may be used to endorse products.\n" +MIT_TEXT = notices.LICENSE_TEMPLATES["MIT"][0] +APACHE_TEXT = notices.LICENSE_TEMPLATES["Apache-2.0"][0] +BSD_2_TEXT = notices.LICENSE_TEMPLATES["BSD-2-Clause"][0].replace("<>", "HOLDER") +BSD_3_TEXT = ( + notices.LICENSE_TEMPLATES["BSD-3-Clause"][0] + .replace("<>", "Example Authors") + .replace("<>", "HOLDER") +) def make_package(root: Path, name: str, license_name: str, license_text: str) -> Path: @@ -100,6 +91,23 @@ def test_truncated_approved_license_text_fails_closed(self): with self.assertRaises(notices.NoticeError): notices.normalize_license_id(declared, text) + def test_apache_headings_without_standard_terms_fail_closed(self): + headings_only = """Apache License Version 2.0 +Terms and Conditions for Use, Reproduction, and Distribution +1. Definitions. +2. Grant of Copyright License. +3. Grant of Patent License. +4. Redistribution. +5. Submission of Contributions. +6. Trademarks. +7. Disclaimer of Warranty. +8. Limitation of Liability. +9. Accepting Warranty or Additional Liability. +END OF TERMS AND CONDITIONS""" + + with self.assertRaises(notices.NoticeError): + notices.normalize_license_id("Apache-2.0", headings_only) + def test_copyright_clauses_are_not_rendered_as_attribution(self): self.assertEqual( notices._copyright_lines( From 98cdcd6dd0fd52f1a2be7980191750d76e539aea Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:12:39 +0800 Subject: [PATCH 16/19] fix: warn on additional license restrictions --- scripts/third_party_notices.py | 15 +++++++++++++++ scripts/third_party_notices.test.py | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/scripts/third_party_notices.py b/scripts/third_party_notices.py index 3cccbd50fa..8d8945228b 100644 --- a/scripts/third_party_notices.py +++ b/scripts/third_party_notices.py @@ -25,6 +25,10 @@ LICENSE_BASENAMES = ("LICENSE", "COPYING") NOTICE_BASENAMES = ("NOTICE",) PROHIBITED_LICENSE_WORDS = ("GPL", "LGPL", "AGPL", "SSPL", "GENERAL PUBLIC LICENSE", "SERVER SIDE PUBLIC") +ADDITIONAL_RESTRICTION_PATTERN = re.compile( + r"\b(?:non[- ]commercial|commercial use (?:is )?(?:prohibited|forbidden|restricted)|not for commercial (?:use|purposes))\b", + re.IGNORECASE, +) # SPDX license texts, with copyright headers and the non-operative Apache appendix # intentionally omitted. Matching requires the complete operative text in order; @@ -257,6 +261,16 @@ def _reject_prohibited(value: str) -> None: raise NoticeError(f"prohibited license: {value}") +def _warn_additional_restriction(name: str, version: str, license_text: str) -> None: + """Keep the original text, while drawing attention to obvious added restrictions.""" + if ADDITIONAL_RESTRICTION_PATTERN.search(license_text): + print( + f"third_party_notices: warning: {name}@{version} contains a possible additional license restriction; " + "the original text is included in THIRD_PARTY_NOTICES.md and should be reviewed", + file=sys.stderr, + ) + + def _normalized_license_text(value: str) -> str: without_list_markers = re.sub(r"(?m)^\s*(?:\d+\.|\*)\s+", "", value) return re.sub(r"[\"“”]", "", re.sub(r"\s+", " ", without_list_markers)).casefold() @@ -379,6 +393,7 @@ def _component_from_package( license_id = normalize_license_id(declared_license, license_text) except NoticeError as error: raise NoticeError(f"{name}@{version}: {error}") from error + _warn_additional_restriction(name, version, license_text) return Component( name=name, version=version, diff --git a/scripts/third_party_notices.test.py b/scripts/third_party_notices.test.py index c6385d81fa..3c85e86946 100644 --- a/scripts/third_party_notices.test.py +++ b/scripts/third_party_notices.test.py @@ -5,6 +5,7 @@ """Behavior tests for the third-party notice generator.""" import importlib.util +import io import json import sys import tempfile @@ -108,6 +109,19 @@ def test_apache_headings_without_standard_terms_fail_closed(self): with self.assertRaises(notices.NoticeError): notices.normalize_license_id("Apache-2.0", headings_only) + def test_additional_restriction_warns_without_blocking_notice_generation(self): + with tempfile.TemporaryDirectory() as directory: + package = make_package( + Path(directory), "restricted-package", "MIT", MIT_TEXT + "\nCommercial use is prohibited.\n" + ) + stderr = io.StringIO() + + with mock.patch.object(notices.sys, "stderr", stderr): + component = notices.component_from_node_package(package, notices.ReadBudget()) + + self.assertEqual(component.license_id, "MIT") + self.assertIn("possible additional license restriction", stderr.getvalue()) + def test_copyright_clauses_are_not_rendered_as_attribution(self): self.assertEqual( notices._copyright_lines( From bb07fa2e2f5c87e36bbcff9e4a0ebe9b71993c6b Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:48:10 +0800 Subject: [PATCH 17/19] test: verify stale notices CI failure --- THIRD_PARTY_NOTICES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index f15467848e..1409cb7817 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -144,6 +144,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` + + ## github.com/atotto/clipboard v0.1.4 - Component: github.com/atotto/clipboard From 20ee450e248f59582afeb27ae4701c93f43265c3 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:59:19 +0800 Subject: [PATCH 18/19] Revert "test: verify stale notices CI failure" This reverts commit bb07fa2e2f5c87e36bbcff9e4a0ebe9b71993c6b. --- THIRD_PARTY_NOTICES.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 1409cb7817..f15467848e 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -144,8 +144,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` - - ## github.com/atotto/clipboard v0.1.4 - Component: github.com/atotto/clipboard From 4fe1315d8c877911899f40b66cc078f05fe4e334 Mon Sep 17 00:00:00 2001 From: "guokexin.02" <264159873+Tantanz20020918@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:43:39 +0800 Subject: [PATCH 19/19] ci: bound security checks runtime --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00b028d00f..c6d5595548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -475,6 +475,7 @@ jobs: # ── Layer 4: Security & Compliance (parallel with L2-L3) ────────── security: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read pull-requests: read