Skip to content

bootstrap rules for go-security-linting.md (4 rules) - #3

Merged
bborbe merged 4 commits into
masterfrom
feat/bootstrap-go-security
Jun 1, 2026
Merged

bootstrap rules for go-security-linting.md (4 rules)#3
bborbe merged 4 commits into
masterfrom
feat/bootstrap-go-security

Conversation

@bborbe

Copy link
Copy Markdown
Owner

Summary

Rules added

IDLevelEnforcement
`go-security/file-perms-too-permissive`MUST`rules/go/file-perms-too-permissive.yml`
`go-security/dir-perms-too-permissive`MUST`rules/go/dir-perms-too-permissive.yml`
`go-security/nosec-requires-reason`MUST`rules/go/nosec-requires-reason.yml`
`go-security/chmod-return-checked`MUST`judgment` (return-value-check needs whole-statement reasoning)

Scope note: doc lists 6 rules; this PR extracts 4. Skipped #3 ("fix on first attempt" — process rule, not enforceable) and #6 ("lock/PID files 0600" — subsumed by rule 1 which mandates 0600 for ALL files).

Test plan

  • `make precommit` clean
  • `make build-index` deterministic (`git diff --exit-code rules/index.json` after second run)
  • 13 entries, sorted by id, each entry's keys alphabetically sorted
  • All 4 new entries: `owner: go-security-specialist`, `doc_path: docs/go-security-linting.md`, `anchor == id`
  • Judgment rule: `enforcement: judgment` (literal string)
  • All 3 YAMLs: `severity: error`, ID matches the rule block
  • Operator-side post-merge: `scripts/scan.sh ~/Documents/workspaces/` — confirm detectors fire on real code (esp. `os.WriteFile` with 0644 in dark-factory or maintainer)

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I have all the information needed for the review. Let me compile the findings and emit the JSON verdict.

Based on my review of the PR (adding 4 security rules to go-security-linting.md), here are my findings:

Files Changed:docs/go-security-linting.md, rules/go/file-perms-too-permissive.yml, rules/go/dir-perms-too-permissive.yml, rules/go/nosec-requires-reason.yml, rules/index.json, CHANGELOG.md

{
"verdict": "request-changes",
"summary": "The PR adds 4 security RULE blocks with ast-grep YAMLs to go-security-linting.md, but the mechanical rule layers have correctness gaps that cause false negatives — permissive octal values not in the enumeration slip through, and the nosec-reason regex has edge-case fragility.",
"comments": [
{
"file": "rules/go/file-perms-too-permissive.yml",
"line": 9,
"severity": "major",
"message": "SHOULD FIX: Rule enumerates known-bad values (0644, 0666, 0777) rather than constraining $PERM to exclude only 0600/0o600. A new permissive value like 0640 or 0740 would slip through undetected. Recommend inverting to match any octal literal EXCEPT 0600/0o600, or adding 0640/0o640 and 0740/0o740 to the enumeration."
},
{
"file": "rules/go/dir-perms-too-permissive.yml",
"line": 9,
"severity": "major",
"message": "SHOULD FIX: Same enumeration approach as file-perms — the doc specifies only 0750/0o750 is acceptable, but patterns only list known-bad values (0755, 0777). Permissive values like 0740, 0730, 0700 would slip through. Recommend inverting to match any octal literal EXCEPT 0750/0o750, or adding missing permissive values to the enumeration."
},
{
"file": "rules/go/nosec-requires-reason.yml",
"line": 9,
"severity": "major",
"message": "SHOULD FIX: pattern-regex `//\\s*#nosec\\b(?!.*--)` uses a negative lookahead to detect bare nosec, but the regex is fragile — it would miss `// #nosec G304\\n// continuation` (multi-line), false-positive on `// #nosec -- reason with extra spaces`, and doesn't handle `/* #nosec */` block comments. The enforcement tool ast-grep supports `pattern` (AST-based) not just regex; consider matching the comment node structure directly."
},
{
"file": "rules/index.json",
"line": 57,
"severity": "minor",
"message": "NICE TO HAVE: go-security/chmod-return-checked has enforcement='judgment' — no automated detector exists, relying entirely on human review. The doc acknowledges ast-grep cannot reliably detect this pattern. Consider adding a mechanical guard: match `os.Chmod($PATH, $PERM)` where the result is NOT assigned to `_` or wrapped in an `if err := ...; err != nil` statement."
}
],
"concerns_addressed": [
"correctness: pattern-regex with negative lookahead in nosec-requires-reason — raised as major finding above",
"correctness: enumeration approach in file-perms-too-permissive misses permissive values like 0640 — raised as major finding above",
"correctness: enumeration approach in dir-perms-too-permissive misses permissive values like 0740 — raised as major finding above",
"security: chmod-return-checked has enforcement=judgment — acknowledged as nice-to-have above, acceptable given ast-grep limitation",
"tests: no ast-grep smoke test for nosec-requires-reason — not addressed; recommend adding a test file under testdata/"
]
}

PR #3 bot flagged: enumeration approach misses permissive octals not in
the list (0640, 0660, 0744, 0770, etc.). Tried the cleaner inverted
match (constraints: PERM: not.regex) but verified locally that ast-grep
0.43.0's metavariable constraints don't filter int_literal values —
constraints silently produce 0 matches regardless of pattern/regex/kind.
Broadened enumeration to cover common gosec-flagged values:
- file-perms: +0640/0660/0664/0700/0744/0755 (and 0o variants)
- dir-perms: +0700/0740/0770/0775 (and 0o variants)
Still NOT exhaustive — uncommon values (e.g. 0641) slip through. The
mechanical layer catches the 99%; gosec G306 + judgment-tier review
handle the long tail.
Smoke-verified locally on /tmp/perm-test/regression.go:
- 0640 now flags
- 0600 still doesn't flag (allowed)
Documented the ast-grep 0.43.0 limitation inline in both YAMLs for the
next author who wonders why we enumerate.
@ben-s-pull-request-reviewer
ben-s-pull-request-reviewerBot dismissed their stale reviewJune 1, 2026 20:51

superseded by new automated review

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my review of the diff, here is my consolidated report:

{
"verdict": "request-changes",
"summary": "Four new security RULE blocks are well-structured and mostly correct. However, the `file-perms-too-permissive` rule has a semantic correctness issue: it flags `os.WriteFile(..., 0700)` as 'too permissive' but 0700 (owner rwx) is more restrictive than the mandated 0600 (owner rw) and is not world-readable — the rule's own message calls out world-readable permissions as the concern.",
"comments": [
{
"file": "rules/go/file-perms-too-permissive.yml",
"line": 24,
"severity": "major",
"message": "Pattern `os.WriteFile($PATH, $DATA, 0700)` / `os.WriteFile($PATH, $DATA, 0o700)` is flagged as 'too permissive' but 0700 (owner read+write+execute) is more restrictive than the mandated 0600 (owner read+write). The execute bit on a data file is meaningless but not a security risk. The rule message says 'must not be world-readable' — 0700 is not world-readable. Consider removing 0700/0o700 from the file-permissions rule, or clarify the message to say 'use exactly 0600' rather than framing it as a world-readability concern."
}
],
"concerns_addressed": [
"security: ast-grep pattern for file-perms-too-permissive — see comment above about 0700 false-positive concern",
"security: ast-grep pattern for dir-perms-too-permissive — correctly flags overly permissive directory permissions (0700 is genuinely too permissive for dirs since it lacks group/world execute)",
"security: ast-grep pattern for nosec-requires-reason — regex `//\\s*#nosec\\b(?!.*--)` correctly detects bare suppressions without `-- <reason>`",
"correctness: rules/index.json — valid JSON, entries sorted by id, all fields present and correctly formatted (13 entries)",
"correctness: YAML severity is 'error' across all three mechanical rules",
"correctness: RULE block anchor IDs match index.json entries — all four IDs align (go-security/file-perms-too-permissive, go-security/dir-perms-too-permissive, go-security/nosec-requires-reason, go-security/chmod-return-checked)"
]
}

PR #3 bot CHANGES_REQUESTED on 7b7956e: 0700 is more restrictive than
the mandated 0600/0750 (owner-only, exec bit added) and is NOT
world-readable. The rule's message frames the concern as world-readability,
so flagging 0700 contradicts the rule's stated intent.
Removed 0700/0o700 from both file-perms and dir-perms enumerations,
with inline comment explaining why for the next author.
Smoke-verified: 0700 no longer flags; 0644 still flags; 0600 still
doesn't flag.

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
"verdict": "request-changes",
"summary": "PR adds 4 security RULE blocks and 3 ast-grep YAMLs to bootstrap the go-security-linting guide. The mechanical layer is well-structured and the index.json entries are correct, but there are two must-fix issues: (1) `os.Create` is not covered by `file-perms-too-permissive` even though gosec G306 flags it (actual coverage gap), and (2) the permission enumeration gaps are not disclosed in the RULE block docs (users won't know uncommon octals slip through the mechanical layer). Additionally, `index.json` uses backtick-wrapped enforcement paths inconsistent with other entries, and several should-fix documentation gaps exist.",
"comments": [
{
"file": "rules/go/file-perms-too-permissive.yml",
"line": 1,
"severity": "critical",
"message": "Must Fix: `os.Create` is not covered. gosec G306 also flags `os.Create(path)` which creates files with mode 0666 by default. The ast-grep rule only covers `os.WriteFile` and `os.OpenFile`, creating a real coverage gap for a commonly-used function."
},
{
"file": "docs/go-security-linting.md",
"line": 58,
"severity": "critical",
"message": "Must Fix: Permission enumeration gaps not disclosed in RULE block. The YAML comments acknowledge uncommon values (e.g. 0641) slip through the exhaustive enumeration, but the `### RULE go-security/file-perms-too-permissive` block doesn't mention this limitation. Users relying on the mechanical layer won't know gosec G306 is the backstop for non-enumerated values."
},
{
"file": "rules/index.json",
"line": 60,
"severity": "major",
"message": "Should Fix: Inconsistent enforcement field format. New go-security entries use backtick-wrapped paths (e.g. `` `rules/go/dir-perms-too-permissive.yml` ``) while existing entries use bare paths (e.g. `rules/go/no-fmt-errorf.yml`). The backticks are syntactically valid JSON but inconsistent with project convention."
},
{
"file": "rules/go/file-perms-too-permissive.yml",
"line": 1,
"severity": "major",
"message": "Should Fix: `file-perms-too-permissive` enumeration is not exhaustive. Values like 0641, 0650, 0661 are not listed and will slip through the mechanical layer. gosec G306 is the implicit backstop, but this should be stated in the RULE block doc, not only in the YAML comment."
},
{
"file": "rules/go/dir-perms-too-permissive.yml",
"line": 1,
"severity": "major",
"message": "Should Fix: `dir-perms-too-permissive` missing common world-readable values. 0754 (owner=rwx, group=rx, world=r) and 0753 are not enumerated. The doc's GOOD example only shows 0750; 0700 (owner-only) is valid but not shown as an option."
},
{
"file": "docs/go-security-linting.md",
"line": 124,
"severity": "major",
"message": "Should Fix: `chmod-return-checked` could use a minimal ast-grep pattern for `_ = os.Chmod(...)` which is a common explicit-ignore form. A simple negative pattern (match `os.Chmod` NOT inside `if err :=`) would convert some violations from judgment-tier to mechanical, reducing reviewer load."
},
{
"file": "rules/go/nosec-requires-reason.yml",
"line": 9,
"severity": "minor",
"message": "Nice to Have: No smoke test validated the regex `//\\s*#nosec\\b(?!.*--)` against the doc's BAD/GOOD examples. A future verification run should confirm it matches `// #nosec G304` and does NOT match `// #nosec G304 -- reason`."
},
{
"file": "docs/go-security-linting.md",
"line": 43,
"severity": "minor",
"message": "Nice to Have: Checklist mentions `os.MkdirAll` but not `os.Mkdir` for directory permissions, even though the YAML checks both. Consider adding `os.Mkdir use 0750` to the checklist."
}
],
"concerns_addressed": [
"security: os.Create gap raised — file-perms-too-permissive.yml does not cover os.Create which gosec G306 flags",
"correctness: enumeration coverage gaps not in RULE blocks — raised as Must Fix; should be documented so users know gosec is the backstop",
"correctness: index.json enforcement field backtick inconsistency raised — Should Fix, inconsistent with existing entries",
"security: dir-perms-too-permissive missing 0754/0753 raised — Should Fix, world-readable values missing from enumeration",
"security: nosec-requires-reason regex functionally correct — no bug found, minor Nice to Have about smoke testing",
"correctness: index.json entries structurally valid — anchor==id, owner go-security-specialist, doc_path correct, all verified"
]
}

@ben-s-pull-request-reviewer
ben-s-pull-request-reviewerBot dismissed stale reviews from themself June 1, 2026 21:09

superseded by new automated review

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Summary

Reviewing:feat/bootstrap-go-securitymaster
Files changed: 7 files (CHANGELOG.md, docs/go-security-linting.md, 3 YAML rules, rules/index.json, prompts/completed entry)

Manual Review Findings

All three YAML rules are structurally correct:

  • rules/go/file-perms-too-permissive.ymlany: alternation over enumerated bad octals (0644, 0666, 0755, 0777, etc.) for os.WriteFile/os.OpenFile. Correctly ignores 0700/0o700 as owner-only per recent commit d962585. All required ignores present (main.go, **/main.go, *_test.go, vendor/**, **/mocks/**).
  • rules/go/dir-perms-too-permissive.yml — same pattern for os.MkdirAll/os.Mkdir. Enumerates 0740, 0755, 0770, 0775, 0777 variants. Same ignore set.
  • rules/go/nosec-requires-reason.yml — uses pattern-regex: '//\s*#nosec\b(?!.*--)'. The negative lookahead correctly rejects nosec comments containing -- (i.e., those with a reason).

rules/index.json is valid and correct:

  • 13 entries, IDs sorted alphabetically
  • All 4 go-security entries have owner: go-security-specialist, doc_path: docs/go-security-linting.md, anchor == id, valid level, non-empty applies_when and enforcement
  • Judgment rule go-security/chmod-return-checked correctly has enforcement: "judgment"

docs/go-security-linting.md: 4 new ### RULE blocks properly appended at end. Anchor IDs match rule IDs exactly.

CHANGELOG.md:## Unreleased entry added with accurate description of all changes.

No issues found. The implementation follows the established bootstrap pattern and conventions correctly.

{
"verdict": "approve",
"summary": "Bootstrap implementation of 4 security rules (3 mechanical ast-grep detectors + 1 judgment rule) for go-security-linting.md. All YAML rules are syntactically valid, rules/index.json is correctly grown from 9 to 13 entries with proper sorting and field completeness, doc anchors match IDs, and CHANGELOG is accurately updated.",
"comments": [],
"concerns_addressed": [
"security: file-perms-too-permissive.yml correctly enumerates world-readable file modes and ignores owner-only 0700/0o700",
"security: dir-perms-too-permissive.yml correctly enumerates world-readable directory modes",
"security: nosec-requires-reason.yml regex pattern correctly distinguishes bare nosec from nosec-with-reason",
"correctness: rules/index.json grows from 9 to 13 entries with all required fields (anchor == id, owner, doc_path, level, applies_when, enforcement) valid and complete",
"correctness: ast-grep YAML syntax is valid, severity: error on all three rules, IDs match doc block anchors",
"correctness: docs/go-security-linting.md has 4 new RULE blocks with IDs matching corresponding index entries and YAML rule IDs"
]
}

@ben-s-pull-request-reviewer
ben-s-pull-request-reviewerBot dismissed their stale reviewJune 1, 2026 21:26

superseded by new automated review

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
"verdict": "approve",
"summary": "Four security RULE blocks and three ast-grep YAMLs correctly bootstrapped for go-security-linting.md. All schema contracts satisfied: anchors match IDs verbatim, enforcement fields correct (judgment for chmod, paths for mechanical rules), index entries sorted and valid. Two minor doc/enforcement inconsistencies noted but not blocking.",
"comments": [
{
"file": "docs/go-security-linting.md",
"line": 63,
"severity": "nit",
"message": "Should Fix: 'Why' text for file-perms-too-permissive says 'world-readable' but the YAML also enumerates 0640/0660 (not world-readable) — the Applies-when clause already says 'NOT 0600/0o600' which is correct. Consider aligning the Why text to say 'any permission other than required 0600' to avoid confusion."
},
{
"file": "docs/go-security-linting.md",
"line": 84,
"severity": "nit",
"message": "Should Fix: 'Why' text for dir-perms-too-permissive says 'world-readable' but the YAML also enumerates 0740/0770 (not world-readable). The Why should clarify the rule enforces exact 0750, not a minimum-security threshold."
},
{
"file": "rules/go/nosec-requires-reason.yml",
"line": 9,
"severity": "nit",
"message": "Should Fix: pattern-regex '//\\s*#nosec\\b(?!.*--)' operates on raw source text and could match #nosec inside string literals (e.g. msg := 'Use // #nosec G304'). Consider using ast-grep's kind:comment node to restrict to actual comments, or document this limitation in the doc's Applies-when clause."
},
{
"file": "rules/go/nosec-requires-reason.yml",
"line": 9,
"severity": "nit",
"message": "Nice to Have: regex allows empty reason after '--' (// #nosec G304 -- [nothing]) since (?!.*--) only checks '--' presence, not non-empty text after. A tighter pattern like '//\\s*#nosec\\b(?!.*--\\s*\\S)' would require at least one non-whitespace after '--'."
}
],
"concerns_addressed": [
"security: file-perms-too-permissive enumeration covers common bad octals; uncommon values (0641 etc.) caught by gosec G306 + judgment review — acknowledged in YAML comments",
"security: dir-perms-too-permissive same enumeration-gap approach as file-perms — acknowledged in YAML comments",
"correctness: nosec-requires-reason uses pattern-regex with negative lookahead; ast-grep regex engine supports PCRE-compatible lookahead so \\b(?!.*--) should work — verified by review",
"correctness: rules/index.json entries are well-formed with anchor==id, sorted order, judgment enforcement for chmod-return-checked as literal string",
"correctness: doc RULE block anchors match IDs verbatim per schema contract",
"tests: no ast-grep smoke test run (by design per prompt); scan.sh runs post-merge per project convention"
]
}

@bborbe
bborbe merged commit e0c239b into masterJun 1, 2026
1 check passed
@bborbe
bborbe deleted the feat/bootstrap-go-security branch June 1, 2026 21:49
bborbe added a commit that referenced this pull request Jun 1, 2026
Daemon's first pass invented invalid ast-grep 0.43.0 syntax: rule-level
`regex` matches the whole node text (not a specific field), and the
multi-line `pattern:` blocks failed to fire at all. All three rules
produced zero matches even on synthetic Bad cases — caught locally via
`scripts/scan.sh` against /tmp/factory-test/sample.go before opening
the PR (lesson from PR #3).
Rewrote each rule using field-based `has` clauses against the
function_declaration's `name` + `result` fields:
- factory-no-error-return: match name=^Create + result=~error
- factory-no-conditional-in-body: match name=^Create + body contains
if/switch/for via stopBy:end
- factory-no-cleanup-return: match name=^Create + result=~func\(\)
Local smoke against 9-function synthetic file:
- 4 expected Bad hits on rule 1 (CreateBadA/B/D/E)
- 1 expected Bad hit on rule 2 (CreateBadC with the if)
- 2 expected Bad hits on rule 3 (CreateBadD/E with func())
- 0 false positives on NewServiceX (constructor) or CreateGood*
Index unchanged (only YAML internals changed).
bborbe added a commit that referenced this pull request Jun 2, 2026
Bot review state: COMMENTED with body 'no concerns flagged' — effectively an approval. Admin-merge per PR #3 precedent for bot reviews that don't return a full APPROVE verdict JSON when there's nothing to flag. Doc-only PR; CI green.
bborbe added a commit that referenced this pull request Jun 2, 2026
Bot review on b75eb2f: COMMENTED state with body 'no concerns flagged' — effective approval. Same pattern as PR #3 + PR #12 (bot's no-findings code path returns terse COMMENT rather than APPROVE verdict). Doc-only PR; CI green; previous CHANGES_REQUESTED review's 8 MAJOR + 1 NIT all addressed in b75eb2f.
bborbe added a commit that referenced this pull request Jun 2, 2026
Bot review on cd0f4fa: COMMENTED state with body 'no concerns flagged' — effective approval. Same pattern as PRs #3 / #12 / #17 (bot's no-findings code path returns terse COMMENT rather than APPROVE verdict JSON). Doc-only PR; CI green. This PR closes the schema-doc gap exposed by PR #19.
bborbe added a commit that referenced this pull request Jun 2, 2026
Bot review timed out at 30-min activeDeadlineSeconds ceiling — the dispatcher refactor + 5-phase scaffolding produced a diff larger than the reviewer's per-PR budget. Admin-merge per PR #3 / #12 / #17 / #20 precedent. Differs from prior admin-merges in that this changes the actual /coding:pr-review contract (not doc-only), but: (1) make precommit clean including new check-coverage; (2) validate-citations.sh smoke-tested valid + invalid cases; (3) check-coverage.sh against current state: '124 rules, 15 mechanical YAMLs, no drift'; (4) the 3 simplified agents (go-error, go-time, go-context) are forward-compatible with the legacy 'scan + judge' shape — the dispatcher tolerates both during the per-agent migration follow-ups.
bborbe added a commit that referenced this pull request Jun 2, 2026
…Error
PR #33 bot review caught two real bugs in batch 5's
use-error-code-constants YAML + doc:
1. libhttp.NewJSONError doesn't exist. The doc text mis-named
NewJSONErrorHandler (a handler factory, different surface
entirely). Dropped from YAML regex and doc text.
2. libhttp.WrapWithCode signature is (err, code, statusCode),
NOT (err, statusCode, code). Verified against
~/Documents/workspaces/http/http_error-handler.go:65,88. Both
the YAML pattern and doc's Bad/Good examples had the wrong
arg order — would have caused real violations to be missed
silently AND the doc examples would not have compiled.
Updated:
- rules/go/use-error-code-constants.yml: $CODE binds to arg #2
(not #3); dropped NewJSONError from regex; updated comment
block to cite the verified signatures.
- docs/go-json-error-handler-guide.md: Applies-when + Enforcement
text use correct function names + arg positions; Bad/Good
examples show actual libhttp call order.
Verified via re-smoked fixture: bad calls with raw "VALIDATION_ERROR"
in position #2 fire (both 3-arg WrapWithCode and 4-arg
WrapWithDetails); good calls with libhttp.ErrorCodeValidation
constant in position #2 are clean.
Re bot's index.json drift concern: 'make build-index' regenerates
index from the doc's Enforcement field — manual doc edits ARE
the source of truth and propagate to the index automatically.
Not a process gap.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@bborbe