docs: allow approved third-party packages #59
Reference in New Issue
Block a user
Delete Branch "allow-deps"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Updates dependency policy from "stdlib only" to "stdlib preferred, approved packages allowed".
Changes
gopkg.in/yaml.v3— YAML parsing for persona filesgithub.com/google/go-cmp— Test comparisons with readable diffsWhy
The "stdlib only" policy was too restrictive for practical development. Some tasks (YAML parsing, test comparisons) are better served by battle-tested packages than rolling our own.
This unblocks PR #58 (YAML persona support).
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
0e9ddc3c)Sonnet Review
Summary
This PR updates the dependency policy in CONVENTIONS.md from 'stdlib only' to 'stdlib preferred with approved packages', adding
gopkg.in/yaml.v3andgithub.com/google/go-cmpto an initial approved list. The change is purely documentation, CI passed, and the process described is reasonable.Findings
CONVENTIONS.mdgo-cmpis a test-only dependency, but the table doesn't distinguish test dependencies from production dependencies. A 'Scope' column (e.g. 'test only' vs 'production') would clarify thatgo-cmpshould never appear in non-test code, preventing accidental production use.CONVENTIONS.mdRecommendation
APPROVE — Approve. The change is well-scoped documentation update with a clear rationale. Both approved packages are widely used and well-maintained:
gopkg.in/yaml.v3is the canonical Go YAML library andgo-cmpis the Google-maintained test comparison library used throughout the Go ecosystem. The process table and approval criteria are reasonable. The two nits (test-vs-production scope distinction, approval authority) are minor polish items that could be addressed in a follow-up if desired.Review by sonnet
Evaluated against
0e9ddc3c@@ -7,0 +9,4 @@| Package | Use Case | Notes ||---------|----------|-------|| `gopkg.in/yaml.v3` | YAML parsing | Persona files, config |[NIT]
go-cmpis a test-only dependency, but the table doesn't distinguish test dependencies from production dependencies. A 'Scope' column (e.g. 'test only' vs 'production') would clarify thatgo-cmpshould never appear in non-test code, preventing accidental production use.@@ -7,0 +12,4 @@| `gopkg.in/yaml.v3` | YAML parsing | Persona files, config || `github.com/google/go-cmp` | Test comparisons | `cmp.Diff` for readable diffs |To add a new dependency:[NIT] The process for adding new dependencies doesn't mention who has approval authority (e.g. maintainers, a specific review process). Adding a brief note like 'requires approval from a maintainer' would make the governance expectation explicit.
0e9ddc3c57to4b96231b32Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
4b96231b)Sonnet Review
Summary
This PR updates the dependency policy from 'stdlib only' to a strict allowlist approach, adds two approved packages (yaml.v3 and go-cmp), and introduces enforcement tooling via a shell script and Makefile target. CI passed. The changes are well-structured and the enforcement mechanism is sound.
Findings
scripts/check-deps.shscripts/check-deps.shgo list -m allincludes indirect/transitive dependencies. If yaml.v3 or go-cmp pull in their own transitive deps, those would appear here and trigger false-positive violations. Consider using `go mod edit -jsonscripts/check-deps.shecho -eis not POSIX-compliant and behavior varies across shells (e.g., on some systems /bin/bash may not support -e with printf semantics). The\nin the violations string also won't render on all platforms. Consider usingprintfinstead:printf 'The following imports are not in the allowlist:%b\n' "$VIOLATIONS".Recommendation
APPROVE — Approve with minor notes. The policy change is clear, well-documented, and enforced via tooling. The two approved packages (yaml.v3 and go-cmp) are well-chosen battle-tested packages for their respective use cases. The dead ALLOWED_PATTERN variable and the transitive-dependency issue in the shell script are worth fixing in a follow-up, but they don't block this documentation/process PR. The transitive dependency issue in particular could cause noise once yaml.v3 or go-cmp are actually added to go.mod, so it's worth addressing before that happens.
Review by sonnet
Evaluated against
4b96231b@@ -0,0 +11,4 @@)# Build regex pattern from allowed listALLOWED_PATTERN=""[MINOR] The ALLOWED_PATTERN variable is built but never used. The actual checking is done in the loop with direct string comparison against the ALLOWED array. The dead code is harmless but confusing — either use the pattern (e.g., with grep) or remove it.
@@ -0,0 +24,4 @@IMPORTS=$(go list -m all 2>/dev/null | tail -n +2 | awk '{print $1}' || true)if [ -z "$IMPORTS" ]; thenecho "✅ No external dependencies"[MINOR]
go list -m allincludes indirect/transitive dependencies. If yaml.v3 or go-cmp pull in their own transitive deps, those would appear here and trigger false-positive violations. Consider usinggo mod edit -json | jq '.Require[] | select(.Indirect == false) | .Path'or filtering withgo list -m -mod=mod -f '{{if not .Indirect}}{{.Path}}{{end}}' allto check only direct dependencies.@@ -0,0 +48,4 @@done <<< "$IMPORTS"if [ -n "$VIOLATIONS" ]; thenecho "❌ UNAPPROVED DEPENDENCIES DETECTED"[NIT]
echo -eis not POSIX-compliant and behavior varies across shells (e.g., on some systems /bin/bash may not support -e with printf semantics). The\nin the violations string also won't render on all platforms. Consider usingprintfinstead:printf 'The following imports are not in the allowlist:%b\n' "$VIOLATIONS".Security Review
Summary
The changes introduce a strict dependency allowlist with an enforcement script and hook it into the build workflow. Overall this improves supply-chain security. I found minor hardening opportunities in the enforcement script and CI integration but no exploitable vulnerabilities.
Findings
scripts/check-deps.shgo list -m allerrors (`Makefileprecommittarget runs the dependency check, but unless CI invokes this target, policy enforcement may not occur in CI. Ensure CI runsmake precommit(or otherwise invokescheck-deps) to prevent unapproved dependencies from being merged.scripts/check-deps.shALLOWEDarray), creating a risk of drift. A mismatch could either block approved deps or unintentionally allow unapproved ones, weakening policy enforcement. Consider deriving the list from a single source (e.g., parse CONVENTIONS.md or define the list in a single machine-readable file).Recommendation
APPROVE — Overall the policy and automation are a net security improvement and CI has passed, so this can be merged. To strengthen enforcement, update scripts/check-deps.sh to fail closed when
go list -m allfails (e.g., remove|| trueand treat command failure as an error), and ensure your CI pipeline invokesmake precommitsocheck-depsruns on every PR. To avoid policy drift, keep the allowlist in a single source of truth that both documentation and the script consume (e.g., a JSON/YAML file or by parsing the markdown table). These changes will make the dependency policy more robust against supply-chain risks.Review by security
Evaluated against
4b96231b@@ -20,1 +23,4 @@go tool cover -func=coverage.out# Precommit runs all checks required before pushingprecommit: check-deps lint test[MINOR] The new
precommittarget runs the dependency check, but unless CI invokes this target, policy enforcement may not occur in CI. Ensure CI runsmake precommit(or otherwise invokescheck-deps) to prevent unapproved dependencies from being merged.@@ -0,0 +5,4 @@set -euo pipefail# Approved third-party packages (from CONVENTIONS.md)ALLOWED=([MINOR] The allowlist is duplicated between CONVENTIONS.md and this script (
ALLOWEDarray), creating a risk of drift. A mismatch could either block approved deps or unintentionally allow unapproved ones, weakening policy enforcement. Consider deriving the list from a single source (e.g., parse CONVENTIONS.md or define the list in a single machine-readable file).@@ -0,0 +21,4 @@done# Get all imports from go.mod (excluding the module itself and stdlib)IMPORTS=$(go list -m all 2>/dev/null | tail -n +2 | awk '{print $1}' || true)[MINOR] The script fails open if
go list -m allerrors (|| true), then treats an empty IMPORTS as "no external dependencies" and exits 0. This can bypass enforcement if the Go toolchain is unavailable or the command fails. Prefer failing closed on errors or explicitly erroring whengo listfails.Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
4b96231b)Gpt Review
Summary
Good step toward a controlled allowlist-based dependency policy and automated enforcement. However, the enforcement script hard-codes the allowlist, which diverges from the documented process and can cause drift; also a couple of minor Makefile/script nits.
Findings
scripts/check-deps.shCONVENTIONS.mdMakefilescripts/check-deps.shCONVENTIONS.mdRecommendation
REQUEST_CHANGES — The policy change and automation are sensible, but the allowlist must have a single source of truth to avoid drift with the documented process. Please either (a) teach scripts/check-deps.sh to parse the allowlist from CONVENTIONS.md (or a separate machine-readable allowlist file that CONVENTIONS.md references), or (b) update CONVENTIONS.md to explicitly require updating scripts/check-deps.sh in the same PR that updates the table. Also, add 'precommit' to the .PHONY list in the Makefile and remove the unused ALLOWED_PATTERN variable from the script. Consider clarifying in CONVENTIONS.md how transitive dependencies are handled so enforcement expectations are clear. Once these issues are addressed, the PR should be good to merge.
Review by gpt
Evaluated against
4b96231b@@ -7,0 +8,4 @@### Approved Third-Party Packages| Package | Use Case ||---------|----------|[MINOR] Policy text says 'Only packages listed below may be imported' but the enforcement checks modules via 'go list -m all' and will also block any transitive modules not on the list. Consider clarifying that approving a dependency may entail approving its transitive modules, or adjust the script to allow transitive deps of approved modules automatically.
@@ -7,0 +10,4 @@| Package | Use Case ||---------|----------|| `gopkg.in/yaml.v3` | YAML parsing (persona files, config) || `github.com/google/go-cmp` | Test comparisons (`cmp.Diff`) |[MINOR] The dependency request process omits updating the enforcement mechanism. If the allowlist remains hard-coded in scripts/check-deps.sh, instructions should explicitly require updating the script alongside the table, or the script should be changed to derive its allowlist from a single canonical source.
@@ -1,4 +1,4 @@.PHONY: build test test-integration lint clean coverage.PHONY: build test test-integration lint clean coverage check-deps[MINOR] The new 'precommit' target isn't listed in .PHONY. It should be added to avoid make treating a file named 'precommit' as up-to-date and skipping the target.
@@ -0,0 +5,4 @@set -euo pipefail# Approved third-party packages (from CONVENTIONS.md)ALLOWED=([MAJOR] The allowlist is hard-coded in the script (ALLOWED array) while CONVENTIONS.md is presented as the source of truth. The documented process says to open a PR that ONLY updates the table, but the enforcement will still fail later unless this script is updated too. This mismatch will cause policy drift and block subsequent PRs using newly approved dependencies.
@@ -0,0 +12,4 @@# Build regex pattern from allowed listALLOWED_PATTERN=""for pkg in "${ALLOWED[@]}"; do[NIT] ALLOWED_PATTERN is built but never used. Remove this dead code to reduce confusion and keep the script minimal.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
70267b68)Sonnet Review
Summary
This PR makes a clean policy change: replacing the 'stdlib only' rule with a strict allowlist approach, backed by a well-implemented enforcement script. CI passes. The changes are consistent and the enforcement mechanism is sound.
Findings
scripts/check-deps.sh-oPflag for PCRE-style regex (\K) is a GNU grep extension and is not available on macOS (BSD grep). This will silently fail to parse the allowlist on macOS developer machines (the `scripts/check-deps.shgo list -m -f '{{if not .Indirect}}{{.Path}}{{end}}' alllists module-level direct dependencies, not package-level imports. This means a module in go.mod that isn't actually imported in any Go source file will still be flagged. More precisely, it will miss the case where someone adds ago.modentry for an unapproved module but the script will catch modules not in go.mod at all (since they wouldn't be listed). This is probably fine for the intended purpose, but the comment 'DIRECT dependencies only' may mislead — it means direct in the go.mod sense, not 'actually used in source'. Consider a brief clarifying comment.scripts/check-deps.shwc -lon$IMPORTS, but$IMPORTScould contain blank lines depending on thego listoutput format when some template expansions produce empty strings. Thegrep -v '^$'earlier helps but the count may still be off by one if there's a trailing newline. Minor cosmetic issue.CONVENTIONS.mdgithub.com/google/go-cmpwith scope 'test only', but the enforcement script (check-deps.sh) does not differentiate between production and test-only scope — it checks all direct go.mod dependencies uniformly. Ifgo-cmpends up in go.mod as a direct dependency (which it will when used in_test.gofiles), it passes the allowlist check regardless of where it's imported. The 'test only' scope annotation is purely documentation with no mechanical enforcement. This is acceptable but worth noting so future maintainers don't assume it's enforced.Recommendation
APPROVE — Approve. The policy change is well-reasoned, the allowlist is minimal and appropriate (yaml.v3 for production, go-cmp for tests), and the enforcement mechanism is solid. The most notable issue is the GNU grep dependency (
-oP) which will silently fail on macOS — worth a follow-up fix or a note in the README/script. The other findings are minor and don't block merging.Review by sonnet
Evaluated against
70267b68@@ -7,0 +11,4 @@|---------|----------|-------|| `gopkg.in/yaml.v3` | YAML parsing (persona files, config) | production || `github.com/google/go-cmp` | Test comparisons (`cmp.Diff`) | test only |[NIT] The table lists
github.com/google/go-cmpwith scope 'test only', but the enforcement script (check-deps.sh) does not differentiate between production and test-only scope — it checks all direct go.mod dependencies uniformly. Ifgo-cmpends up in go.mod as a direct dependency (which it will when used in_test.gofiles), it passes the allowlist check regardless of where it's imported. The 'test only' scope annotation is purely documentation with no mechanical enforcement. This is acceptable but worth noting so future maintainers don't assume it's enforced.@@ -0,0 +31,4 @@# Get DIRECT dependencies only (exclude indirect/transitive)# Fail closed: if go list fails, we exit non-zeroIMPORTS=$(go list -m -f '{{if not .Indirect}}{{.Path}}{{end}}' all 2>&1) || {[MINOR] The
-oPflag for PCRE-style regex (\K) is a GNU grep extension and is not available on macOS (BSD grep). This will silently fail to parse the allowlist on macOS developer machines (the|| trueswallows the error), leaving ALLOWED empty and triggering the warning but not enforcing anything. Consider usingsedinstead:echo "$line" | sed -n "s/^| \([^`])`./\1/p"`, or document that GNU grep is required.@@ -0,0 +41,4 @@if [ -z "$IMPORTS" ]; thenecho "✅ No external dependencies"exit 0[MINOR]
go list -m -f '{{if not .Indirect}}{{.Path}}{{end}}' alllists module-level direct dependencies, not package-level imports. This means a module in go.mod that isn't actually imported in any Go source file will still be flagged. More precisely, it will miss the case where someone adds ago.modentry for an unapproved module but the script will catch modules not in go.mod at all (since they wouldn't be listed). This is probably fine for the intended purpose, but the comment 'DIRECT dependencies only' may mislead — it means direct in the go.mod sense, not 'actually used in source'. Consider a brief clarifying comment.@@ -0,0 +81,4 @@fiecho "✅ All dependencies are approved"echo " Direct deps: $(echo "$IMPORTS" | wc -l | tr -d ' ')"[NIT] The final line counts direct deps via
wc -lon$IMPORTS, but$IMPORTScould contain blank lines depending on thego listoutput format when some template expansions produce empty strings. Thegrep -v '^$'earlier helps but the count may still be off by one if there's a trailing newline. Minor cosmetic issue.Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
70267b68)Gpt Review
Summary
Good documentation update and a useful enforcement script with Makefile integration. However, the dependency checker does not enforce the documented "Scope" column (e.g., test-only), and relies on GNU grep -P which breaks on macOS, making the enforcement brittle.
Findings
scripts/check-deps.shscripts/check-deps.shscripts/check-deps.shRecommendation
REQUEST_CHANGES — Overall the direction is good: documenting an explicit allowlist and adding a precommit target to enforce it. To align implementation with the documented policy and improve robustness:
With these changes, the enforcement will match the documented policy and work reliably across developer environments. Once addressed, the PR looks good to merge.
Review by gpt
Evaluated against
70267b68@@ -0,0 +17,4 @@# Looks for lines like: | `gopkg.in/yaml.v3` | ...ALLOWED=()while IFS= read -r line; do# Extract package from markdown table cell: | `package` |[MAJOR] The script does not enforce the 'Scope' column from CONVENTIONS.md (e.g., allowing github.com/google/go-cmp only in tests). As written, any listed package is allowed in production code, contradicting the documented policy.
@@ -0,0 +20,4 @@# Extract package from markdown table cell: | `package` |pkg=$(echo "$line" | grep -oP '\| `\K[^`]+' | head -1 || true)if [ -n "$pkg" ] && [[ "$pkg" != "Package" ]]; thenALLOWED+=("$pkg")[MINOR] Uses 'grep -P' (Perl regex), which is not available on macOS/BSD grep by default. This reduces cross-platform developer usability for the precommit hook.
@@ -0,0 +23,4 @@ALLOWED+=("$pkg")fidone < <(grep -E '^\| `[a-zA-Z]' "$CONVENTIONS_FILE" || true)[MINOR] Table parsing relies on a brittle grep pattern ('^| `[a-zA-Z]') and a single-code-cell extraction. Future formatting changes (leading spaces, additional code spans) or package names starting with non-letters may cause false negatives/positives.
Security Review
Summary
The PR introduces a strict, documented dependency allowlist and enforces it via a precommit script. CI passed and no exploitable vulnerabilities are introduced; the enforcement script is generally sound.
Findings
scripts/check-deps.shRecommendation
APPROVE — Approve the changes. The dependency allowlist policy and enforcement improve supply chain hygiene without introducing security risks. As a defense-in-depth hardening, modify the dependency match to use a literal prefix comparison rather than glob patterns (e.g., use parameter expansion: if || ; then MATCHED=true; fi). This prevents any accidental glob characters in the allowlist from broadening matches. Otherwise, the implementation is sound and aligns with the documented process.
Review by security
Evaluated against
70267b68@@ -0,0 +51,4 @@# Check if import matches any allowed package (prefix match for subpackages)MATCHED=falsefor allowed in "${ALLOWED[@]}"; doif [[ "$import" == "$allowed" ]] || [[ "$import" == "$allowed/"* ]]; then[MINOR] The prefix match uses , which treats the right side as a glob pattern. While allowed entries are controlled via review, a metacharacter (e.g., *, ?, [) in an approved package could unintentionally broaden matches. Consider a literal prefix check to avoid glob semantics.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
aeb0c8cb)Sonnet Review
Summary
This PR replaces the 'stdlib only' policy with a well-structured strict allowlist enforced by a new check-deps.sh script and Makefile target. The changes are coherent, CI passes, and the enforcement tooling is solid. A few minor issues in the shell script deserve attention but none are blockers.
Findings
scripts/check-deps.shgo list -moutput includes the current module itself on the first line, and the comment says 'Filter out the module itself (first line)'. However,tail -n +2strips the first line unconditionally. Ifgo list -m ... allever reorders output or includes a blank first line (e.g., when run in a workspace), this assumption breaks silently. A more robust approach would be to filter by comparing against$(go list -m)(the current module path) rather than relying on line position.scripts/check-deps.shscripts/check-deps.shgrep -q "^${test_pkg}"without anchoring the end of the pattern. A package path likegithub.com/google/go-cmpwould inadvertently match a hypotheticalgithub.com/google/go-cmp-extended. Should be `grep -qE "^${test_pkg}(/scripts/check-deps.shdeclare -A(associative arrays) is a bash 4+ feature and is not POSIX. The comment should say 'bash 4+' to accurately describe the requirement. macOS ships bash 3.2 by default (GPLv2 licensing constraint), so this script would fail on stock macOS without Homebrew bash. Worth documenting the bash version requirement in a shebang comment or README.CONVENTIONS.mdgo list -m). This is correct behavior—you generally don't want to enumerate all transitive deps—but the wording could be clearer: something like 'Transitive dependencies pulled in by approved packages do not need to be listed here' to make it clear this is intentional, not an oversight.Recommendation
APPROVE — Approve. CI passes and the change is coherent and well-designed. The policy documentation is clear, the single-source-of-truth approach (parsing CONVENTIONS.md) is smart, and the Makefile integration via
precommittarget is exactly right. The MINOR findings (grep anchoring bug, bash version caveat) are worth a follow-up but don't block merging—the grep issue in particular could produce a false negative in an unlikely edge case but would not produce a false positive (it wouldn't miss real violations). The NIT about transitive dep wording is purely cosmetic.Review by sonnet
Evaluated against
aeb0c8cb@@ -7,0 +17,4 @@Transitive dependencies of approved packages are automatically allowed.To request a new dependency:1. Open a PR that ONLY updates this table[NIT] The sentence 'Transitive dependencies of approved packages are automatically allowed' is a policy statement that the check-deps.sh script does NOT currently verify (it only checks direct deps via
go list -m). This is correct behavior—you generally don't want to enumerate all transitive deps—but the wording could be clearer: something like 'Transitive dependencies pulled in by approved packages do not need to be listed here' to make it clear this is intentional, not an oversight.@@ -0,0 +19,4 @@# Output: package:scope (e.g., "gopkg.in/yaml.v3:production")declare -A ALLOWED_PROD=()declare -A ALLOWED_TEST=()[NIT] The comment says 'POSIX-compatible' but
declare -A(associative arrays) is a bash 4+ feature and is not POSIX. The comment should say 'bash 4+' to accurately describe the requirement. macOS ships bash 3.2 by default (GPLv2 licensing constraint), so this script would fail on stock macOS without Homebrew bash. Worth documenting the bash version requirement in a shebang comment or README.@@ -0,0 +65,4 @@# Fail closed: if go list fails, we exit non-zeroIMPORTS=$(go list -m -f '{{if not .Indirect}}{{.Path}}{{end}}' all 2>&1) || {echo "❌ Failed to list dependencies: $IMPORTS"exit 1[MINOR] The
go list -moutput includes the current module itself on the first line, and the comment says 'Filter out the module itself (first line)'. However,tail -n +2strips the first line unconditionally. Ifgo list -m ... allever reorders output or includes a blank first line (e.g., when run in a workspace), this assumption breaks silently. A more robust approach would be to filter by comparing against$(go list -m)(the current module path) rather than relying on line position.@@ -0,0 +96,4 @@exit 1fi# Enforce Scope: test-only packages must not appear in non-test code[MINOR] The test-scope enforcement uses
go list -deps -f '...' ./... | grep -v '_test'to identify production imports, but this approach is fragile: it filters by path containing '_test', which catches test packages but the-depsflag on non-test packages already excludes test files. More importantly, this will fail to detect a test-only package imported via a non-test file in a sub-package whose path happens not to contain '_test'. Usinggo list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' $(go list ./...)(without-deps) and then checking against the allowlist directly would be more precise. That said, the current approach is a reasonable heuristic.@@ -0,0 +105,4 @@if echo "$PROD_IMPORTS" | grep -q "^${test_pkg}"; thenTEST_ONLY_IN_PROD="${TEST_ONLY_IN_PROD} - ${test_pkg} (marked 'test only' but used in production code)"$'\n'fidone[MINOR] The test-only production check uses
grep -q "^${test_pkg}"without anchoring the end of the pattern. A package path likegithub.com/google/go-cmpwould inadvertently match a hypotheticalgithub.com/google/go-cmp-extended. Should begrep -qE "^${test_pkg}(/|$)"to match only exact or subpackage imports.Security Review
Summary
Changes add a strict allowlist for third-party dependencies with an enforcement script and integrate it into the build. CI passed and the enforcement logic is generally sound with no exploitable vulnerabilities found.
Findings
scripts/check-deps.shscripts/check-deps.shRecommendation
APPROVE — Overall, the dependency policy update and enforcement mechanism look solid and CI passed. Approve the PR. As hardening improvements, consider: (1) escaping package names when grepping for test-only packages in production (e.g., use grep -F or escape metacharacters and match ^(/|$) to avoid regex-induced mismatches); and (2) avoid swallowing errors when listing production imports—if go list -deps fails, exit non-zero rather than proceeding, to fail closed. These changes will make enforcement more reliable without altering intended behavior.
Review by security
Evaluated against
aeb0c8cb@@ -0,0 +98,4 @@# Enforce Scope: test-only packages must not appear in non-test code# Get imports used by non-test code onlyPROD_IMPORTS=$(go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' ./... 2>/dev/null | grep -v '_test' || true)[MINOR] Production imports discovery allows go list to fail open (2>/dev/null | ... || true). If go list fails, PROD_IMPORTS becomes empty and the script won’t detect misuse of test-only dependencies, weakening enforcement. Failing closed here would improve robustness.
@@ -0,0 +102,4 @@TEST_ONLY_IN_PROD=""for test_pkg in "${!ALLOWED_TEST[@]}"; doif echo "$PROD_IMPORTS" | grep -q "^${test_pkg}"; then[MINOR] The grep check for test-only packages in production uses an unescaped regex pattern (grep -q "^${test_pkg}"). If a package name contains regex metacharacters (e.g., dots), this may match unintended imports. While not a code execution risk, it can cause false positives/negatives in enforcement.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
aeb0c8cb)Gpt Review
Summary
The policy update and Makefile integration look good, but the dependency enforcement script has a critical gap: it does not validate test-only dependencies against the allowlist, allowing unapproved test deps to slip through. There are also a couple of robustness issues in prefix matching and portability.
Findings
scripts/check-deps.shgo list -m ... alland never verifies test-only dependencies against the allowlist. As a result, importing any new third-party package in tests (e.g., in *_test.go) will not be flagged unless also used in production code. This violates the documented 'STRICT ALLOWLIST' policy for tests.scripts/check-deps.shgrep -q "^${test_pkg}"which can produce false positives for modules with a similar prefix (e.g., github.com/google/go-cmppro would match github.com/google/go-cmp). Use a delimiter-aware pattern like `^${test_pkg}(/scripts/check-deps.shdeclare -A), which require Bash 4+. On macOS (default Bash 3.2), this will fail locally for contributors runningmake precommit. Consider documenting the requirement, invoking a known shell (e.g., viaenv bashwith ensured version), or rewriting to avoid associative arrays.scripts/check-deps.shgrep -v '_test'in the production imports step is unnecessary becausego list -depswithout-testalready excludes test-only dependencies. While harmless, it can be removed for clarity.Recommendation
REQUEST_CHANGES — Overall the policy change and Makefile additions are sensible, but please fix the enforcement gap before merging. Specifically: (1) Extend the script to validate test dependencies against the allowlist. For example, run
go list -test -m -f '{{if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all(or a suitablego list -deps -testvariant that maps packages to modules) and ensure each test module matches either the production or test-only allowlist. This will prevent unapproved test-only packages from being introduced. (2) Tighten the regex used to detect test-only packages in production code to^${test_pkg}(/|$)to avoid false positives on similarly prefixed modules. (3) Address portability by documenting a Bash 4+ requirement forprecommitor refactoring to avoid associative arrays so local contributors on macOS aren't blocked. (4) Optionally remove the redundant_testfilter in the production imports step for clarity. After these changes, the script will correctly enforce the strict allowlist for both production and test dependencies as documented, and the PR can be approved.Review by gpt
Evaluated against
aeb0c8cb@@ -0,0 +17,4 @@# Parse approved packages from CONVENTIONS.md table using awk (POSIX-compatible)# Format: | `package` | use case | scope |# Output: package:scope (e.g., "gopkg.in/yaml.v3:production")declare -A ALLOWED_PROD=()[MINOR] The script relies on bash associative arrays (
declare -A), which require Bash 4+. On macOS (default Bash 3.2), this will fail locally for contributors runningmake precommit. Consider documenting the requirement, invoking a known shell (e.g., viaenv bashwith ensured version), or rewriting to avoid associative arrays.@@ -0,0 +62,4 @@}# Get DIRECT dependencies only (exclude indirect/transitive)# Fail closed: if go list fails, we exit non-zero[MAJOR] The script checks only direct non-test modules via
go list -m ... alland never verifies test-only dependencies against the allowlist. As a result, importing any new third-party package in tests (e.g., in *_test.go) will not be flagged unless also used in production code. This violates the documented 'STRICT ALLOWLIST' policy for tests.@@ -0,0 +96,4 @@exit 1fi# Enforce Scope: test-only packages must not appear in non-test code[NIT] The
grep -v '_test'in the production imports step is unnecessary becausego list -depswithout-testalready excludes test-only dependencies. While harmless, it can be removed for clarity.@@ -0,0 +100,4 @@# Get imports used by non-test code onlyPROD_IMPORTS=$(go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' ./... 2>/dev/null | grep -v '_test' || true)TEST_ONLY_IN_PROD=""[MINOR] The scope enforcement uses
grep -q "^${test_pkg}"which can produce false positives for modules with a similar prefix (e.g., github.com/google/go-cmppro would match github.com/google/go-cmp). Use a delimiter-aware pattern like^${test_pkg}(/|$)to avoid accidental matches.Sonnet Review
Summary
This PR replaces the blunt 'stdlib only' rule with a well-structured allowlist system, complete with enforcement tooling. The CONVENTIONS.md change is clear and the check-deps.sh script is solid overall. CI passes. A few minor issues in the script are worth noting but don't block merge.
Findings
scripts/check-deps.shgo.moddirect dependencies for allowlist compliance, but the scope enforcement (lines 97-111) checks the full production import graph viago list -deps. These two checks are at different granularities and could diverge. For example, a direct dependency might be test-only in practice butgo list -deps ./...(which doesn't filter test files) would still traverse it. Usinggo list -deps -test=false ./...more explicitly conveys intent, though the current flag-less form already excludes test builds.scripts/check-deps.shscripts/check-deps.shgo listreturns non-module output (e.g. build errors) the error is captured inDIRECT_IMPORTSand the early-exit error message will contain the raw go toolchain output. This works adequately but the error message could be confusing. Minor quality-of-life issue.scripts/check-deps.sh< <(...)which is Bash-specific. The comment is mildly misleading — it means awk itself is POSIX, not the overall approach.CONVENTIONS.mdgo list -m ... allwith.Indirectfiltered out). This is correct and intentional, but it's worth confirming the wording matches: transitive deps won't appear as violations, which aligns with the statement.Recommendation
APPROVE — Approve. The change is well-reasoned, well-documented, and the enforcement script is functional and correct for its intended purpose. CI passes. The minor issues noted (regex redundancy, comment accuracy) are cosmetic and do not affect correctness. The policy itself — strict allowlist with a clear process for additions — is a significant improvement over either 'stdlib only' or uncontrolled dependency growth.
Review by sonnet
Evaluated against
01cde16d@@ -7,0 +11,4 @@|---------|----------|-------|| `gopkg.in/yaml.v3` | YAML parsing (persona files, config) | production || `github.com/google/go-cmp` | Test comparisons (`cmp.Diff`) | test only |[NIT] The note 'Transitive dependencies of approved packages are automatically allowed' is a policy statement, but the enforcement script only checks direct module dependencies (via
go list -m ... allwith.Indirectfiltered out). This is correct and intentional, but it's worth confirming the wording matches: transitive deps won't appear as violations, which aligns with the statement.@@ -0,0 +54,4 @@local import="$1"shiftlocal -n allowlist=$1[NIT] The comment says 'POSIX-compatible' for the awk parsing but the outer loop uses a Bash process substitution
< <(...)which is Bash-specific. The comment is mildly misleading — it means awk itself is POSIX, not the overall approach.@@ -0,0 +68,4 @@return 1}# Get direct module dependencies from go.mod[MINOR] If
go listreturns non-module output (e.g. build errors) the error is captured inDIRECT_IMPORTSand the early-exit error message will contain the raw go toolchain output. This works adequately but the error message could be confusing. Minor quality-of-life issue.@@ -0,0 +70,4 @@# Get direct module dependencies from go.modDIRECT_IMPORTS=$(go list -m -f '{{if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all 2>&1) || {echo "❌ Failed to list dependencies: $DIRECT_IMPORTS"[MINOR] The script checks
go.moddirect dependencies for allowlist compliance, but the scope enforcement (lines 97-111) checks the full production import graph viago list -deps. These two checks are at different granularities and could diverge. For example, a direct dependency might be test-only in practice butgo list -deps ./...(which doesn't filter test files) would still traverse it. Usinggo list -deps -test=false ./...more explicitly conveys intent, though the current flag-less form already excludes test builds.@@ -0,0 +101,4 @@fi# Enforce Scope: test-only packages must not appear in non-test code# Get imports used by non-test code only (go list -deps without -test excludes test deps)[MINOR] The regex
"^${test_pkg}(/|\$|$)"has a redundant$—\$escapes the literal dollar sign for the shell but in the regex context insidegrep -qE, the trailing|$)is a proper end-of-line anchor followed by the closing group, making\$(literal$character) unreachable in practice. This is harmless but the intent (match exact package or subpackage) is already handled by the^pkg($|/)pattern. Consider simplifying to"^${test_pkg}(/|$)".Gpt Review
Summary
The dependency policy update and enforcement script look sound. The Makefile integration is clean, and CI has passed. The new check-deps.sh correctly enforces an allowlist, distinguishes test-only deps, and tolerates subpackages.
Findings
scripts/check-deps.sh[[ "$pkg" =~ ^[a-zA-Z] ]]rejects valid import paths that begin with a digit (e.g., 9fans.net/go). Consider relaxing to^[[:alnum:]]or removing the check, since the header row is already excluded by the grep.scripts/check-deps.shscripts/check-deps.shgrep/awkis somewhat brittle (e.g., relies on backticks around package names and exact column positions). This is acceptable given the documented process, but a brief note in CONVENTIONS.md to preserve formatting would help avoid accidental breakage.MakefileRecommendation
APPROVE — Overall, the changes are well-implemented and align with the updated policy. The allowlist enforcement correctly checks direct module dependencies and prevents test-only packages from being used in production code. Consider relaxing the package-name filter to allow domains that start with digits, simplifying a redundant regex in the production check, and adding a small note about preserving the markdown table format and the Bash 4+ requirement in developer docs. With these minor tweaks, this will be robust and maintainable. Approving as-is.
Review by gpt
Evaluated against
01cde16d@@ -12,9 +12,15 @@ test-integration:lint:go vet ./...check-deps:[NIT] The check-deps script requires Bash 4+. On macOS, default /bin/bash is 3.x; while the script emits guidance, you might consider documenting this requirement alongside the new precommit target for developer setup clarity.
@@ -0,0 +29,4 @@declare -A ALLOWED_TEST=()while IFS= read -r line; do# Use awk to extract package and scope from table row[NIT] Parsing the markdown table with
grep/awkis somewhat brittle (e.g., relies on backticks around package names and exact column positions). This is acceptable given the documented process, but a brief note in CONVENTIONS.md to preserve formatting would help avoid accidental breakage.@@ -0,0 +32,4 @@# Use awk to extract package and scope from table rowpkg=$(echo "$line" | awk -F'|' '{gsub(/^[[:space:]]*`|`[[:space:]]*$/, "", $2); print $2}')scope=$(echo "$line" | awk -F'|' '{gsub(/^[[:space:]]+|[[:space:]]+$/, "", $4); print tolower($4)}')[MINOR] The filter
[[ "$pkg" =~ ^[a-zA-Z] ]]rejects valid import paths that begin with a digit (e.g., 9fans.net/go). Consider relaxing to^[[:alnum:]]or removing the check, since the header row is already excluded by the grep.@@ -0,0 +106,4 @@TEST_ONLY_IN_PROD=""for test_pkg in "${!ALLOWED_TEST[@]}"; do# Use word-boundary matching: exact match or followed by /[MINOR] The grep regex
^${test_pkg}(/|\$|$)redundantly includes both\$and$. It can be simplified to^${test_pkg}(/|$)without changing semantics.Security Review
Summary
Changes introduce a strict dependency allowlist with an enforcement script and integrate it into the precommit workflow. CI passed and the additions do not introduce exploitable vulnerabilities.
Recommendation
APPROVE — The new policy and check-deps script strengthen supply-chain controls by enforcing an explicit allowlist and test-only constraints. Consider future hardening (e.g., escaping regex metacharacters in grep patterns and tightening the Bash version check for namerefs), but these are not security-critical. Proceed with merge.
Review by security
Evaluated against
01cde16dReview Feedback Addressed
Pushed commit
0619e2bwith the following changes:Fixed (5 findings)
[MINOR] Line 35 - import path filter (sonnet + gpt): Relaxed
^[a-zA-Z]to^[[:alnum:]]to accept valid paths starting with digits (e.g.,9fans.net/go).[MINOR] Line 99/109 - redundant
\$in regex (sonnet + gpt): Simplified^${test_pkg}(/|\$|$)to^${test_pkg}(/|$)— the\$(literal dollar) was unreachable.[MINOR] Line 71 - stderr mixing (sonnet): Now captures stderr to a temp file to avoid mixing error messages with package list output.
[NIT] Line 26 comment (sonnet): Updated misleading "POSIX-compatible" comment — the loop uses Bash process substitution
< <(...).[NIT] CONVENTIONS.md line 17 (sonnet): Clarified wording: "Only direct dependencies (listed in go.mod without
// indirect) are checked against this allowlist. Transitive dependencies pulled in by approved packages are implicitly allowed."Deferred (2 findings)
Markdown table parsing brittleness (gpt NIT): The existing note in CONVENTIONS.md already warns that the script parses the table. Additional documentation provides low value.
Bash 4+ macOS documentation (gpt NIT): The script already has a runtime check with clear error message and instructions (
brew install bash). Duplicating in Makefile is redundant.All tests pass:
go test ./...✅