Skip to content

fix(rules): hardcoded_tmp stops teaching its own false positive; corpus + inline directives honoured - #881

Merged
hyperpolymath merged 3 commits into
mainfrom
fix/hardcoded-tmp-rule-cure
Sep 30, 2026
Merged

hyperpolymath merged 3 commits into
mainfrom
fix/hardcoded-tmp-rule-cure

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Stacked on #877 (itself on #876). The base retargets to main once #877 lands. Squash automerge will be armed then, not before: armed now, it would merge into #877's branch.

Why

A Hypatia hardcoded_tmp alert on launch-scaffolder#46 exposed a real CWE-377 defect in the launcher generator. That was fixed upstream in launch-scaffolder #54/#58/#62. The rule itself was part of the problem:

Input Before After
PID_FILE="/tmp/x.pid" fires fires (positive control)
d=$(mktemp -d /tmp/foo.XXXXXX), the rule's own advice fires clean
# comment mentioning "/tmp/" fires clean
/tmp line under tests/fixtures/, test/, lib/rules/, scripts/fix-scripts/ fires suppressed
# hypatia: allow content_patterns/hardcoded_tmp -- … silently ignored honoured

Changes

  • Remediation text. It now separates pid/state files, which the next run must re-find (per-user ${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}}/<app>/), from scratch files (mktemp -d with no /tmp template, plus trap … EXIT). The old text said "use mktemp", which is the wrong cure for a pid file.
  • Own-cure skip. skip_comment_lines: true, plus a new generic rule key skip_if_line_matches (here ~r/\bmktemp\b/).
  • Training-corpus exemption. Added under "content_patterns", the key the findings are emitted under in cli.ex. A "cicd_rules" key would be vacuous.
  • Inline directives. hypatia: allow <module>/<rule> is now honoured by the content engine under either module spelling. Rule ids are atoms, so they are stringified before inline_allowed?/4.
  • Line-1 fix. Line 1 no longer treats the file's last line as its "previous line" (Enum.at(lines, -1) wraps around).
  • Docs. The .hypatia-ignore header and .claude/CLAUDE.md now document the two-spelling trap and the directive behaviour.

Not changed, deliberately

  • applies_to stays ["*.sh"]. Widening it to *.rs would flag #[cfg(test)] literals under src/, and cicd_rules has no Rust comment stripping.
  • The file-level hypatia: allow form is still not wired for content patterns. CLAUDE.md now says so.

Trade-off, stated plainly

After this PR, fixtures under tests/ are no longer scanned for this rule. On the launch-scaffolder side, the generator's own literal-pinned tests (DEFAULT_PID_LINE) are now the detector for a /tmp regression.

Verification

  • test/hardcoded_tmp_pipeline_test.exs has 16 tests running the real pipeline (collect_findings → normalise → suppress), never a hand-built finding.

  • Four mutants were each killed:

    Mutant Red tests
    Exemption key renamed 4
    mktemp skip removed 1
    n > 1 guard reverted 1
    inline_allowed? clause removed 3
  • Full suite: 1689 tests, 0 failures. mix compile --warnings-as-errors --force is clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 896165fb-386b-4a56-84ff-b9f8b277ff84

📥 Commits

Reviewing files that changed from the base of the PR and between 948f311 and a1bd2fb.

📒 Files selected for processing (5)
  • .claude/CLAUDE.md
  • .hypatia-ignore
  • lib/hypatia/scanner_suppression.ex
  • lib/rules/cicd_rules.ex
  • test/hardcoded_tmp_pipeline_test.exs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

hyperpolymath and others added 2 commits September 30, 2026 11:08
…us + inline directives honoured

The rule's remediation said "use mktemp", yet `mktemp -d /tmp/x.XXXXXX`
fired the rule, and mktemp is the wrong cure for a pid file the next run
must re-find. Now:

* reason text distinguishes pid/state files (per-user XDG ladder) from
  scratch files (mktemp -d, no /tmp template, trap cleanup);
* skip_comment_lines + new skip_if_line_matches (~r/\bmktemp\b/) guard;
* "content_patterns" joins @default_exemptions for the training corpus.
  The key is the EMITTED rule_module (cli.ex normalises content findings
  to "content_patterns"); a "cicd_rules" key would be vacuous;
* `hypatia: allow <module>/<rule>` is now honoured by the content engine
  under either module spelling (was silently inert; only the bare
  `hypatia:ignore <rule>` needle worked). Rule ids are atoms, so the id
  is stringified before inline_allowed?/4;
* line 1 no longer reads the LAST line as its "previous line"
  (Enum.at(lines, -1) wraparound);
* .hypatia-ignore header and CLAUDE.md document the two-spelling trap.

test/hardcoded_tmp_pipeline_test.exs runs the real pipeline
(collect_findings -> normalise -> suppress). Four mutants each killed:
exemption key renamed (4 red), mktemp skip removed (1 red), n>1 guard
reverted (1 red), inline_allowed? clause removed (3 red).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK
…anner matches fragments

The two readers of this file ask different questions. The gate is the
stricter one on purpose (per-path ledger), so the header now says so
instead of letting a directory fragment quiet only half the pipeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK
@hyperpolymath
hyperpolymath force-pushed the fix/hardcoded-tmp-rule-cure branch from 05ade6d to 07806f8 Compare September 30, 2026 10:08
@hyperpolymath
hyperpolymath changed the base branch from fix/pin-integrity-latent-logic to main September 30, 2026 10:08
@hyperpolymath
hyperpolymath enabled auto-merge (squash) September 30, 2026 10:11
@hyperpolymath
hyperpolymath merged commit 36f8d36 into main Sep 30, 2026
39 of 45 checks passed
@hyperpolymath
hyperpolymath deleted the fix/hardcoded-tmp-rule-cure branch September 30, 2026 10:21
hyperpolymath added a commit that referenced this pull request Sep 30, 2026
Resolves conflicts after #875 squash-merged and #881/#882 landed:
cicd_rules hardcoded_tmp takes main's mktemp skip; workflow_hardening
keeps with_local_scripts; scanner_suppression test takes main's
fixture-based form. mix test: 1713 tests, 0 failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFphKkDVB9pUDSCD4bkz65
hyperpolymath added a commit to hyperpolymath/standards that referenced this pull request Sep 30, 2026
…1076)

Part C of the launcher `/tmp` cure (origin: a Hypatia
`content_patterns/hardcoded_tmp` alert on launch-scaffolder#46).

## launcher-standard — deed + adoc in lock-step
- `:pid-file-pattern` →
`${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}}/launch-scaffolder/{app-name}/server.pid`
- `:log-file-pattern` →
`${XDG_STATE_HOME:-$HOME/.local/state}/launch-scaffolder/{app-name}/server.log`

The runtime hunk is copied **verbatim** from launch-scaffolder `main`'s
baked `standards/launcher-standard_praxis.deed` (#54/#58/#62). Its
generator already emits this ladder. The old `${TMPDIR:-/tmp}` last
resort was the CWE-377 target named by the standard's own rationale: a
predictable name in a world-writable dir lets another user choose which
PID `stop` kills. The adoc template, the `--disinteg` list, the
debugging checklist, logging and Security sections all move with it.

**No `:standard-version` bump, deliberately.** launch-scaffolder already
bakes this ladder at 0.4.0. A bump would desync the two copies and make
`check-launcher-standard-currency.sh` fail every 0.4.0 claim. Launchers
on the old ladder are now non-conforming by design; the A8 re-mint sweep
reaches them.

**Out of scope, noted:** the baked copy has also grown `platforms`,
`lifecycle-phases` and `metadata-block.encoding` clauses that this
canonical file lacks. That is a separate reconciliation.

## QUICKSTART
It taught `PID_FILE="/tmp/myapp-server.pid"` and friends in seven
places, contradicting the standard in the same directory. This is the
likely seed of the ~17 shipped `/tmp` launchers. All are replaced; the
`hardcoded_tmp` regex `["'/]tmp/` now matches nothing in the file.

## EXEMPTION-MECHANISMS
The document said `.hypatia-ignore` is *"never read by anything"*, and
told reviewers to reject it. It also said Hypatia *"ignores comments"*.
Both are false. The new **Layer 2a** covers:
- the two consumers and how each matches (scanner: substring fragment;
governance gate: whole-line exact path);
- why the gate is deliberately the stricter one;
- the emitted-module trap (`content_patterns/…`, not `cicd_rules/…`);
- the suppression ladder.

The anti-pattern list now rejects directory, wildcard and wrong-module
lines, and invented pragmas, instead of the file itself.

Why the gate matcher is **not** loosened to substring matching: gate ⊂
scanner, so the mismatch can only false-*block*, never false-pass.
Containment would let a `…:src/` line absorb every future banned file. A
census of 266 local `.hypatia-ignore` files found **0** directory,
wildcard or `/`-terminated lines for the two gate rules, so no repo is
currently hit by the divergence.

⚠ Inline `hypatia: allow` on content-pattern rules needs
hyperpolymath/hypatia#881 (armed).

### Added after review (e0c96f7, a530b71)

QUICKSTART Step 1 copies `comprehensive-launcher-template.sh`, which
still wrote pid/log to `/tmp` and created no directory — so the doc and
the file it hands the reader disagreed, and the XDG ladder would fail on
the first `nohup … > "$LOG_FILE"`. The comprehensive, dustfile and
e-grade templates now use the ladder and `mkdir -p -m 0700` the leaf
directory before first write (SC2174 is intended: only the per-app leaf
needs 0700). README checklist and a soft-attach.sh usage example no
longer teach `/tmp`. After this, the only `/tmp` mentions under
`docs/UX-standards` and `launcher/` are launcher-standard.adoc’s
explicit prohibitions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to 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