opencode-era record. Superseded where moot by the pi migration (ADR-0055). Retained as historical context.
Date: 2026-07-14
Accepted
The @semgrep agent currently relies entirely on generic registry packs
(p/php, p/secrets, p/javascript) for SAST scanning. These packs are
language-generic — they cannot encode:
- Aurora-specific footguns: the constructor's positional
$statusbool (a literaltrueleaks stack traces and SQL fragments to visitors). - No-framework sinks:
$db->query("...$var...")without bound parameters, unescapedecho $_REQUEST[...], andunserialize(on request-reachable data are caught by generic rules only in their most obvious forms, if at all. - Project conventions:
ini_set('display_errors', '1')in application code is a violation of Aurora's error-handling contract (the constructor's$statusparameter, wired toAPP_DEBUGviaenv_bool(), is the single point of control).
The security-coding skill (.opencode/skills/security-coding/SKILL.md)
documents these defensive patterns in prose, but the detection layer (@semgrep)
has no project-specific teeth. The skill and the scanner are decoupled.
Trail of Bits offers a semgrep-rule-creator skill in their Codex marketplace,
advocating test-driven rule authoring — write positive/negative fixtures, then
implement the rule, validated by a test harness. This approach maps cleanly to
the repository's TDD conventions (Pest + Red→Green→Refactor).
We maintain a first-party Semgrep rules pack at .semgrep/kyaulabs.yml, loaded
by @semgrep on every invocation alongside the registry packs. Every rule has
positive and negative PHP fixtures in tests/Semgrep/<RuleId>/ and is
validated by tests/Unit/Semgrep/RulesPackTest.php — a Pest test that asserts
each rule fires on its positive fixture and does not fire on its negative
fixture.
Dual coverage design: some rules overlap with Pest arch/integration tests
(e.g., AuroraConstructorStatusTest.php scans for hardcoded $status=true at
the PHP level). The Semgrep rules provide the same detection at a different
pipeline point — during @semgrep diff audits (pre-commit review) — while
Pest tests provide the hard gate at /check time. Both are intentional:
diff-time early warning + test-time enforcement.
Rule-authoring convention: new rules follow TDD:
- Write positive fixture (code that SHOULD trigger the rule).
- Write negative fixture (code that SHOULD NOT trigger).
- Add one row to
semgrepRulesProvider()inRulesPackTest.php— a['dir' => <Dir>, 'rule' => <rule-id>, 'positive' => <expected count>]entry. The positive and negative dataset tests project from this single source of truth. - Run the test — it fails (Red).
- Implement the rule in
.semgrep/kyaulabs.yml. - Run the test — it passes (Green).
- Refactor the rule for precision/performance.
Suppression policy: findings may be suppressed inline with
// nosemgrep: <rule-id> -- <justification>. Bare // nosemgrep is
forbidden. Suppressions are re-reviewed when the named rule is updated.
See the /security command's false-positive adjudication protocol.
Metadata convention (issue #104): each rule carries a metadata:
block with category: security, a cwe: ID + description, technology: [php|generic], confidence: and likelihood: ratings (HIGH/MEDIUM/LOW),
and references: pointing to relevant CWE/OWASP pages and ADR-0002. These
blocks are informational — they document the security taxonomy and enable
SARIF consumers to display categorized findings without affecting rule
matching.
- Easier: Aurora-specific footguns and no-framework sinks are detected
automatically at review time; the
security-codingskill's defensive guidance now has machine-enforced backup; the TDD convention keeps rules honest (no untested rules). - Harder: new contributors must understand the TDD rule-authoring convention; the rules pack adds a maintenance surface (though fixtures make regressions immediately visible).
- Dependency: requires
semgrepon the build/review host. Already a soft-requirement for/checkand@semgrep— no new tooling. - Sync enforcement (issue #94): a test in
RulesPackTest.phpkeeps the rules pack,semgrepRulesProvider(), and thetests/Semgrep/<Dir>/fixtures in lock-step. It extracts rule ids from.semgrep/kyaulabs.yml, asserts set-equality with the provider, verifies each provider directory has bothpositive.phpandnegative.php, and rejects orphan fixture directories. A rule added to the YAML without a provider row + fixtures now fails the test suite — the "no untested rules" invariant is mechanized, not aspirational. --x-ignore-semgrepignore-filesdependency: the test harness scanstests/Semgrep/even though.semgrepignoreexcludes it (intentionally vulnerable SAST fixtures must not pollute normal scans). It relies on semgrep's experimental--x-ignore-semgrepignore-filesflag to bypass that exclusion during validation. Two guards pin it: the scan's exit code is asserted0(so a failed scan cannot let negatives pass vacuously on empty results), andsemgrep scan --helpis checked for the flag name as an early-warning canary against rename/removal. If the flag graduates (drops thex-prefix) or is removed, updatesemgrepScanAll()and the canary assertion.- CSRF regex expansion (issue #104): the
pattern-regexforkyaulabs-missing-csrf-tokenwas changed from a strict</form>requirement to(?:</form>|\z), catching forms without closing tags. The regex continues to match the full form body so thatpattern-not-regex: '(?i)csrf'(which checks the matched region, not the file level) can detect CSRF token hidden inputs and suppress findings. Theseverity: INFOand "review manually" message mitigate potential false positives from matching inside HTML comments.
- Registry-only (status quo) — no project-specific detection. Rejected:
the gap between
security-codingprose and@semgrepscans is real and growing. - Pest-only detection — writing PHP tests for every pattern duplicates what Semgrep does at the AST level. Rejected: complement, don't replace. Dual coverage at different pipeline points is the design.
- External rules repository — a separate repo for the rules pack adds an indirection and a versioning problem. Rejected: keeping rules colocated with the code they protect ensures they evolve together.