{"owner":"buger","repo":"jsonparser","hasSkills":true,"totalSkillsCount":1,"totalTokensCount":1964,"categories":["anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["proof/signals/rules/unchecked-caller-slice-deref.yaml"],"skills":{"proof/signals/rules/unchecked-caller-slice-deref.yaml":"# Project-specific code signal rule for the jsonparser proof project.\n#\n# Purpose\n#   Catch every unchecked `[]` dereference on a caller-influenced slice\n#   (keys / data / p) so the next variant of the empty-key panic class\n#   surfaces as a finding instead of as a runtime panic.\n#\n# Origin / motivation\n#   The empty-key hazard sweep flagged 7 unguarded `keys[i][0]` sites but\n#   missed parser.go:1000 (the \"8th site\", cited as :981 in the prior\n#   revision) because the dereference used the slice-expression form\n#   `keys[depth:][0][0]`. The sweep's mental pattern was\n#   `identifier[index][0]`, which does not match slice subscripts. This\n#   rule treats slice-expression-then-index and direct-index-then-field\n#   as the SAME hazard class.\n#\n# Obligation mapping\n#   boundary  — behavior at the edges of valid input ranges (empty\n#               collections, single-element, exactly-at-limit values).\n#               The boundary class is already owned by the parser\n#               requirement family, so a finding opens a real review\n#               item without minting a new obligation row.\n#\n#   (panic_free_input_handling is the more semantically precise class\n#   for this hazard, but it is not yet in this project's obligation_classes\n#   list and is not owned by any parser requirement. Mapping to boundary\n#   alone keeps the audit honest: each finding surfaces against a class\n#   the project has actually agreed to track. Adding\n#   panic_free_input_handling to obligation_classes + a requirement\n#   checklist is the right follow-up once the empty-key hazard sweep\n#   is formalized — see PROOF_UNDER_MODELED_REQUIREMENTS_PROPOSAL.md.)\n#\n# Local dev / CI install\n#   opengrep is the analyzer backend. It is npm-installable:\n#     npm install -g @opengrep/cli@1.22.0\n#   proof's `tools.opengrep.auto_download: true` (set in proof.yaml) also\n#   fetches the pinned version on first `proof signals collect`.\n#\n# Verification\n#   The rule was validated against parser.go: it fires on the unguarded\n#   `keys[depth:][0][0]` site (line 1000) and on the bare\n#   `keys[i][0]`, `keys[i][j]`, `p[level][0]`, and computed-offset\n#   `data[$F(...)]` / `data[$I + $N]` shapes. It does NOT fire on the\n#   guarded sites at parser.go:410 (`len(keys[level]) > 0 && keys[level][0]`)\n#   or parser.go:835 (`len(keys[lk-1]) > 0 && string(keys[lk-1][0])`),\n#   nor on `for i := range data { ... data[i] ... }` bounded loops.\n\nrules:\n  - id: jsonparser.boundary.unchecked-caller-slice-deref\n    message: >-\n      Unchecked [] dereference on a caller-influenced slice (keys / data / p).\n      The 8th empty-key panic site (parser.go:1000, cited as :981 in the\n      prior revision) used the slice-expression variant\n      `keys[depth:][0][0]` — a form the earlier identifier[index][0]\n      hazard sweep missed because slice subscripts do not match the\n      `identifier[index]` mental pattern. Require an explicit\n      `len(X) > N` / `N < len(X)` guard on the SAME variable in the same\n      control-flow block before any `X[i]`, `X[i:j][k]`, or `X[i][j]`\n      dereference on a caller-controlled slice, or move the access\n      behind a typed error path. (The semantically precise class is\n      panic_free_input_handling; this rule maps to boundary because\n      that is the obligation the parser requirement family already\n      owns — see file header.)\n    languages: [go]\n    severity: WARNING\n    metadata:\n      proof:\n        signal_id: jsonparser.unchecked_caller_slice_deref\n        provider: opengrep\n        tags: [panic_safety, boundary, accepts_user_data, binary_safe_io]\n        obligations: [boundary]\n        confidence: medium\n        framework_hints: [\"CWE-125\", \"CWE-129\", \"CWE-787\"]\n    patterns:\n      - pattern-either:\n          # form 3 — slice-expression then index (the MISSED parser.go:1000 form)\n          # Matches `keys[depth:][0][0]`, `data[i:][0]`, `keys[i:j][k]`, etc.\n          - pattern: $X[$I:$J][$K]\n          - pattern: $X[$I:$J][$K][$L]\n          - pattern: $X[:$J][$K]\n          - pattern: $X[:$J][$K][$L]\n          - pattern: $X[$I:][$K]\n          - pattern: $X[$I:][$K][$L]\n          # forms 1, 2 — direct index then field / nested direct index\n          # Matches `keys[i][0]`, `keys[i][j]`, `p[level][0]`.\n          - pattern: $X[$I][$J]\n          - pattern: $X[$I][$J][$K]\n          # form 4 — byte-slice index where the offset is a tokenizer-return\n          # expression or arithmetic-derived offset (the actually dangerous\n          # shape). A bare `data[i]` inside a bounded for-loop is\n          # intentionally NOT matched — only computed offsets that can\n          # outrun the slice are flagged. Matches:\n          #   data[nextToken(...)]\n          #   data[startOffset+1+nextToken(data[startOffset+1:])]\n          #   data[startOffset + 1]\n          - pattern: $X[$F(...)]\n          - pattern: $X[$I + $F(...)]\n          - pattern: $X[$F(...) + $N]\n          - pattern: $X[$I + $N]\n      - metavariable-regex:\n          metavariable: $X\n          regex: \"^(keys|data|p|bytes|buf|src|dst|path|paths|segs|segments)$\"\n      # ---- Expression-level guards ----\n      # Exclude any dereference whose enclosing expression already\n      # short-circuited on a `len(X) > N` / `len(X) >= N` / `N < len(X)`\n      # / `len(X) == 0` / `len(X) < N` check (on any variable — same-block\n      # guard hygiene is what matters, not name identity).\n      - pattern-not-inside: len($G) > $N && $REST\n      - pattern-not-inside: $PRE && len($G) > $N && $REST\n      - pattern-not-inside: len($G) >= $N && $REST\n      - pattern-not-inside: $PRE && len($G) >= $N && $REST\n      - pattern-not-inside: $N < len($G) && $REST\n      - pattern-not-inside: $PRE && $N < len($G) && $REST\n      - pattern-not-inside: $N <= len($G) && $REST\n      - pattern-not-inside: len($G) == 0 || $REST\n      - pattern-not-inside: $PRE || len($G) == 0 || $REST\n      - pattern-not-inside: len($G) < $N || $REST\n      - pattern-not-inside: $PRE || len($G) < $N || $REST\n      # ---- Block-level guards ----\n      - pattern-not-inside: |\n          if len($G) > $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N < len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N <= len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) == 0 {\n            ...\n            return ...\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) > $N && $COND {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N && $COND {\n            ...\n          }\n      # ---- Bounded for-loop on the slice ----\n      - pattern-not-inside: |\n          for $I := range data {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := range $D {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len(data); $I++ {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len($D); $I++ {\n            ...\n          }\n"},"files":{"proof/signals/rules/unchecked-caller-slice-deref.yaml":"# Project-specific code signal rule for the jsonparser proof project.\n#\n# Purpose\n#   Catch every unchecked `[]` dereference on a caller-influenced slice\n#   (keys / data / p) so the next variant of the empty-key panic class\n#   surfaces as a finding instead of as a runtime panic.\n#\n# Origin / motivation\n#   The empty-key hazard sweep flagged 7 unguarded `keys[i][0]` sites but\n#   missed parser.go:1000 (the \"8th site\", cited as :981 in the prior\n#   revision) because the dereference used the slice-expression form\n#   `keys[depth:][0][0]`. The sweep's mental pattern was\n#   `identifier[index][0]`, which does not match slice subscripts. This\n#   rule treats slice-expression-then-index and direct-index-then-field\n#   as the SAME hazard class.\n#\n# Obligation mapping\n#   boundary  — behavior at the edges of valid input ranges (empty\n#               collections, single-element, exactly-at-limit values).\n#               The boundary class is already owned by the parser\n#               requirement family, so a finding opens a real review\n#               item without minting a new obligation row.\n#\n#   (panic_free_input_handling is the more semantically precise class\n#   for this hazard, but it is not yet in this project's obligation_classes\n#   list and is not owned by any parser requirement. Mapping to boundary\n#   alone keeps the audit honest: each finding surfaces against a class\n#   the project has actually agreed to track. Adding\n#   panic_free_input_handling to obligation_classes + a requirement\n#   checklist is the right follow-up once the empty-key hazard sweep\n#   is formalized — see PROOF_UNDER_MODELED_REQUIREMENTS_PROPOSAL.md.)\n#\n# Local dev / CI install\n#   opengrep is the analyzer backend. It is npm-installable:\n#     npm install -g @opengrep/cli@1.22.0\n#   proof's `tools.opengrep.auto_download: true` (set in proof.yaml) also\n#   fetches the pinned version on first `proof signals collect`.\n#\n# Verification\n#   The rule was validated against parser.go: it fires on the unguarded\n#   `keys[depth:][0][0]` site (line 1000) and on the bare\n#   `keys[i][0]`, `keys[i][j]`, `p[level][0]`, and computed-offset\n#   `data[$F(...)]` / `data[$I + $N]` shapes. It does NOT fire on the\n#   guarded sites at parser.go:410 (`len(keys[level]) > 0 && keys[level][0]`)\n#   or parser.go:835 (`len(keys[lk-1]) > 0 && string(keys[lk-1][0])`),\n#   nor on `for i := range data { ... data[i] ... }` bounded loops.\n\nrules:\n  - id: jsonparser.boundary.unchecked-caller-slice-deref\n    message: >-\n      Unchecked [] dereference on a caller-influenced slice (keys / data / p).\n      The 8th empty-key panic site (parser.go:1000, cited as :981 in the\n      prior revision) used the slice-expression variant\n      `keys[depth:][0][0]` — a form the earlier identifier[index][0]\n      hazard sweep missed because slice subscripts do not match the\n      `identifier[index]` mental pattern. Require an explicit\n      `len(X) > N` / `N < len(X)` guard on the SAME variable in the same\n      control-flow block before any `X[i]`, `X[i:j][k]`, or `X[i][j]`\n      dereference on a caller-controlled slice, or move the access\n      behind a typed error path. (The semantically precise class is\n      panic_free_input_handling; this rule maps to boundary because\n      that is the obligation the parser requirement family already\n      owns — see file header.)\n    languages: [go]\n    severity: WARNING\n    metadata:\n      proof:\n        signal_id: jsonparser.unchecked_caller_slice_deref\n        provider: opengrep\n        tags: [panic_safety, boundary, accepts_user_data, binary_safe_io]\n        obligations: [boundary]\n        confidence: medium\n        framework_hints: [\"CWE-125\", \"CWE-129\", \"CWE-787\"]\n    patterns:\n      - pattern-either:\n          # form 3 — slice-expression then index (the MISSED parser.go:1000 form)\n          # Matches `keys[depth:][0][0]`, `data[i:][0]`, `keys[i:j][k]`, etc.\n          - pattern: $X[$I:$J][$K]\n          - pattern: $X[$I:$J][$K][$L]\n          - pattern: $X[:$J][$K]\n          - pattern: $X[:$J][$K][$L]\n          - pattern: $X[$I:][$K]\n          - pattern: $X[$I:][$K][$L]\n          # forms 1, 2 — direct index then field / nested direct index\n          # Matches `keys[i][0]`, `keys[i][j]`, `p[level][0]`.\n          - pattern: $X[$I][$J]\n          - pattern: $X[$I][$J][$K]\n          # form 4 — byte-slice index where the offset is a tokenizer-return\n          # expression or arithmetic-derived offset (the actually dangerous\n          # shape). A bare `data[i]` inside a bounded for-loop is\n          # intentionally NOT matched — only computed offsets that can\n          # outrun the slice are flagged. Matches:\n          #   data[nextToken(...)]\n          #   data[startOffset+1+nextToken(data[startOffset+1:])]\n          #   data[startOffset + 1]\n          - pattern: $X[$F(...)]\n          - pattern: $X[$I + $F(...)]\n          - pattern: $X[$F(...) + $N]\n          - pattern: $X[$I + $N]\n      - metavariable-regex:\n          metavariable: $X\n          regex: \"^(keys|data|p|bytes|buf|src|dst|path|paths|segs|segments)$\"\n      # ---- Expression-level guards ----\n      # Exclude any dereference whose enclosing expression already\n      # short-circuited on a `len(X) > N` / `len(X) >= N` / `N < len(X)`\n      # / `len(X) == 0` / `len(X) < N` check (on any variable — same-block\n      # guard hygiene is what matters, not name identity).\n      - pattern-not-inside: len($G) > $N && $REST\n      - pattern-not-inside: $PRE && len($G) > $N && $REST\n      - pattern-not-inside: len($G) >= $N && $REST\n      - pattern-not-inside: $PRE && len($G) >= $N && $REST\n      - pattern-not-inside: $N < len($G) && $REST\n      - pattern-not-inside: $PRE && $N < len($G) && $REST\n      - pattern-not-inside: $N <= len($G) && $REST\n      - pattern-not-inside: len($G) == 0 || $REST\n      - pattern-not-inside: $PRE || len($G) == 0 || $REST\n      - pattern-not-inside: len($G) < $N || $REST\n      - pattern-not-inside: $PRE || len($G) < $N || $REST\n      # ---- Block-level guards ----\n      - pattern-not-inside: |\n          if len($G) > $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N < len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N <= len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) == 0 {\n            ...\n            return ...\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) > $N && $COND {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N && $COND {\n            ...\n          }\n      # ---- Bounded for-loop on the slice ----\n      - pattern-not-inside: |\n          for $I := range data {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := range $D {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len(data); $I++ {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len($D); $I++ {\n            ...\n          }\n"},"items":[{"name":"unchecked-caller-slice-deref.yaml","path":"proof/signals/rules/unchecked-caller-slice-deref.yaml","rawUrl":"https://raw.githubusercontent.com/buger/jsonparser/HEAD/proof/signals/rules/unchecked-caller-slice-deref.yaml","title":"Rules Skill","category":"anthropic-skill","format":"yaml","content":"# Project-specific code signal rule for the jsonparser proof project.\n#\n# Purpose\n#   Catch every unchecked `[]` dereference on a caller-influenced slice\n#   (keys / data / p) so the next variant of the empty-key panic class\n#   surfaces as a finding instead of as a runtime panic.\n#\n# Origin / motivation\n#   The empty-key hazard sweep flagged 7 unguarded `keys[i][0]` sites but\n#   missed parser.go:1000 (the \"8th site\", cited as :981 in the prior\n#   revision) because the dereference used the slice-expression form\n#   `keys[depth:][0][0]`. The sweep's mental pattern was\n#   `identifier[index][0]`, which does not match slice subscripts. This\n#   rule treats slice-expression-then-index and direct-index-then-field\n#   as the SAME hazard class.\n#\n# Obligation mapping\n#   boundary  — behavior at the edges of valid input ranges (empty\n#               collections, single-element, exactly-at-limit values).\n#               The boundary class is already owned by the parser\n#               requirement family, so a finding opens a real review\n#               item without minting a new obligation row.\n#\n#   (panic_free_input_handling is the more semantically precise class\n#   for this hazard, but it is not yet in this project's obligation_classes\n#   list and is not owned by any parser requirement. Mapping to boundary\n#   alone keeps the audit honest: each finding surfaces against a class\n#   the project has actually agreed to track. Adding\n#   panic_free_input_handling to obligation_classes + a requirement\n#   checklist is the right follow-up once the empty-key hazard sweep\n#   is formalized — see PROOF_UNDER_MODELED_REQUIREMENTS_PROPOSAL.md.)\n#\n# Local dev / CI install\n#   opengrep is the analyzer backend. It is npm-installable:\n#     npm install -g @opengrep/cli@1.22.0\n#   proof's `tools.opengrep.auto_download: true` (set in proof.yaml) also\n#   fetches the pinned version on first `proof signals collect`.\n#\n# Verification\n#   The rule was validated against parser.go: it fires on the unguarded\n#   `keys[depth:][0][0]` site (line 1000) and on the bare\n#   `keys[i][0]`, `keys[i][j]`, `p[level][0]`, and computed-offset\n#   `data[$F(...)]` / `data[$I + $N]` shapes. It does NOT fire on the\n#   guarded sites at parser.go:410 (`len(keys[level]) > 0 && keys[level][0]`)\n#   or parser.go:835 (`len(keys[lk-1]) > 0 && string(keys[lk-1][0])`),\n#   nor on `for i := range data { ... data[i] ... }` bounded loops.\n\nrules:\n  - id: jsonparser.boundary.unchecked-caller-slice-deref\n    message: >-\n      Unchecked [] dereference on a caller-influenced slice (keys / data / p).\n      The 8th empty-key panic site (parser.go:1000, cited as :981 in the\n      prior revision) used the slice-expression variant\n      `keys[depth:][0][0]` — a form the earlier identifier[index][0]\n      hazard sweep missed because slice subscripts do not match the\n      `identifier[index]` mental pattern. Require an explicit\n      `len(X) > N` / `N < len(X)` guard on the SAME variable in the same\n      control-flow block before any `X[i]`, `X[i:j][k]`, or `X[i][j]`\n      dereference on a caller-controlled slice, or move the access\n      behind a typed error path. (The semantically precise class is\n      panic_free_input_handling; this rule maps to boundary because\n      that is the obligation the parser requirement family already\n      owns — see file header.)\n    languages: [go]\n    severity: WARNING\n    metadata:\n      proof:\n        signal_id: jsonparser.unchecked_caller_slice_deref\n        provider: opengrep\n        tags: [panic_safety, boundary, accepts_user_data, binary_safe_io]\n        obligations: [boundary]\n        confidence: medium\n        framework_hints: [\"CWE-125\", \"CWE-129\", \"CWE-787\"]\n    patterns:\n      - pattern-either:\n          # form 3 — slice-expression then index (the MISSED parser.go:1000 form)\n          # Matches `keys[depth:][0][0]`, `data[i:][0]`, `keys[i:j][k]`, etc.\n          - pattern: $X[$I:$J][$K]\n          - pattern: $X[$I:$J][$K][$L]\n          - pattern: $X[:$J][$K]\n          - pattern: $X[:$J][$K][$L]\n          - pattern: $X[$I:][$K]\n          - pattern: $X[$I:][$K][$L]\n          # forms 1, 2 — direct index then field / nested direct index\n          # Matches `keys[i][0]`, `keys[i][j]`, `p[level][0]`.\n          - pattern: $X[$I][$J]\n          - pattern: $X[$I][$J][$K]\n          # form 4 — byte-slice index where the offset is a tokenizer-return\n          # expression or arithmetic-derived offset (the actually dangerous\n          # shape). A bare `data[i]` inside a bounded for-loop is\n          # intentionally NOT matched — only computed offsets that can\n          # outrun the slice are flagged. Matches:\n          #   data[nextToken(...)]\n          #   data[startOffset+1+nextToken(data[startOffset+1:])]\n          #   data[startOffset + 1]\n          - pattern: $X[$F(...)]\n          - pattern: $X[$I + $F(...)]\n          - pattern: $X[$F(...) + $N]\n          - pattern: $X[$I + $N]\n      - metavariable-regex:\n          metavariable: $X\n          regex: \"^(keys|data|p|bytes|buf|src|dst|path|paths|segs|segments)$\"\n      # ---- Expression-level guards ----\n      # Exclude any dereference whose enclosing expression already\n      # short-circuited on a `len(X) > N` / `len(X) >= N` / `N < len(X)`\n      # / `len(X) == 0` / `len(X) < N` check (on any variable — same-block\n      # guard hygiene is what matters, not name identity).\n      - pattern-not-inside: len($G) > $N && $REST\n      - pattern-not-inside: $PRE && len($G) > $N && $REST\n      - pattern-not-inside: len($G) >= $N && $REST\n      - pattern-not-inside: $PRE && len($G) >= $N && $REST\n      - pattern-not-inside: $N < len($G) && $REST\n      - pattern-not-inside: $PRE && $N < len($G) && $REST\n      - pattern-not-inside: $N <= len($G) && $REST\n      - pattern-not-inside: len($G) == 0 || $REST\n      - pattern-not-inside: $PRE || len($G) == 0 || $REST\n      - pattern-not-inside: len($G) < $N || $REST\n      - pattern-not-inside: $PRE || len($G) < $N || $REST\n      # ---- Block-level guards ----\n      - pattern-not-inside: |\n          if len($G) > $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N < len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N <= len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) == 0 {\n            ...\n            return ...\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) > $N && $COND {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N && $COND {\n            ...\n          }\n      # ---- Bounded for-loop on the slice ----\n      - pattern-not-inside: |\n          for $I := range data {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := range $D {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len(data); $I++ {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len($D); $I++ {\n            ...\n          }\n","isInternal":false,"tokens":1964,"sizeBytes":7135}],"systemPromptSnippet":"<yakaai_skills repo=\"buger/jsonparser\">\n<!-- File: proof/signals/rules/unchecked-caller-slice-deref.yaml (Tokens: ~1964 | Category: anthropic-skill) -->\n# Project-specific code signal rule for the jsonparser proof project.\n#\n# Purpose\n#   Catch every unchecked `[]` dereference on a caller-influenced slice\n#   (keys / data / p) so the next variant of the empty-key panic class\n#   surfaces as a finding instead of as a runtime panic.\n#\n# Origin / motivation\n#   The empty-key hazard sweep flagged 7 unguarded `keys[i][0]` sites but\n#   missed parser.go:1000 (the \"8th site\", cited as :981 in the prior\n#   revision) because the dereference used the slice-expression form\n#   `keys[depth:][0][0]`. The sweep's mental pattern was\n#   `identifier[index][0]`, which does not match slice subscripts. This\n#   rule treats slice-expression-then-index and direct-index-then-field\n#   as the SAME hazard class.\n#\n# Obligation mapping\n#   boundary  — behavior at the edges of valid input ranges (empty\n#               collections, single-element, exactly-at-limit values).\n#               The boundary class is already owned by the parser\n#               requirement family, so a finding opens a real review\n#               item without minting a new obligation row.\n#\n#   (panic_free_input_handling is the more semantically precise class\n#   for this hazard, but it is not yet in this project's obligation_classes\n#   list and is not owned by any parser requirement. Mapping to boundary\n#   alone keeps the audit honest: each finding surfaces against a class\n#   the project has actually agreed to track. Adding\n#   panic_free_input_handling to obligation_classes + a requirement\n#   checklist is the right follow-up once the empty-key hazard sweep\n#   is formalized — see PROOF_UNDER_MODELED_REQUIREMENTS_PROPOSAL.md.)\n#\n# Local dev / CI install\n#   opengrep is the analyzer backend. It is npm-installable:\n#     npm install -g @opengrep/cli@1.22.0\n#   proof's `tools.opengrep.auto_download: true` (set in proof.yaml) also\n#   fetches the pinned version on first `proof signals collect`.\n#\n# Verification\n#   The rule was validated against parser.go: it fires on the unguarded\n#   `keys[depth:][0][0]` site (line 1000) and on the bare\n#   `keys[i][0]`, `keys[i][j]`, `p[level][0]`, and computed-offset\n#   `data[$F(...)]` / `data[$I + $N]` shapes. It does NOT fire on the\n#   guarded sites at parser.go:410 (`len(keys[level]) > 0 && keys[level][0]`)\n#   or parser.go:835 (`len(keys[lk-1]) > 0 && string(keys[lk-1][0])`),\n#   nor on `for i := range data { ... data[i] ... }` bounded loops.\n\nrules:\n  - id: jsonparser.boundary.unchecked-caller-slice-deref\n    message: >-\n      Unchecked [] dereference on a caller-influenced slice (keys / data / p).\n      The 8th empty-key panic site (parser.go:1000, cited as :981 in the\n      prior revision) used the slice-expression variant\n      `keys[depth:][0][0]` — a form the earlier identifier[index][0]\n      hazard sweep missed because slice subscripts do not match the\n      `identifier[index]` mental pattern. Require an explicit\n      `len(X) > N` / `N < len(X)` guard on the SAME variable in the same\n      control-flow block before any `X[i]`, `X[i:j][k]`, or `X[i][j]`\n      dereference on a caller-controlled slice, or move the access\n      behind a typed error path. (The semantically precise class is\n      panic_free_input_handling; this rule maps to boundary because\n      that is the obligation the parser requirement family already\n      owns — see file header.)\n    languages: [go]\n    severity: WARNING\n    metadata:\n      proof:\n        signal_id: jsonparser.unchecked_caller_slice_deref\n        provider: opengrep\n        tags: [panic_safety, boundary, accepts_user_data, binary_safe_io]\n        obligations: [boundary]\n        confidence: medium\n        framework_hints: [\"CWE-125\", \"CWE-129\", \"CWE-787\"]\n    patterns:\n      - pattern-either:\n          # form 3 — slice-expression then index (the MISSED parser.go:1000 form)\n          # Matches `keys[depth:][0][0]`, `data[i:][0]`, `keys[i:j][k]`, etc.\n          - pattern: $X[$I:$J][$K]\n          - pattern: $X[$I:$J][$K][$L]\n          - pattern: $X[:$J][$K]\n          - pattern: $X[:$J][$K][$L]\n          - pattern: $X[$I:][$K]\n          - pattern: $X[$I:][$K][$L]\n          # forms 1, 2 — direct index then field / nested direct index\n          # Matches `keys[i][0]`, `keys[i][j]`, `p[level][0]`.\n          - pattern: $X[$I][$J]\n          - pattern: $X[$I][$J][$K]\n          # form 4 — byte-slice index where the offset is a tokenizer-return\n          # expression or arithmetic-derived offset (the actually dangerous\n          # shape). A bare `data[i]` inside a bounded for-loop is\n          # intentionally NOT matched — only computed offsets that can\n          # outrun the slice are flagged. Matches:\n          #   data[nextToken(...)]\n          #   data[startOffset+1+nextToken(data[startOffset+1:])]\n          #   data[startOffset + 1]\n          - pattern: $X[$F(...)]\n          - pattern: $X[$I + $F(...)]\n          - pattern: $X[$F(...) + $N]\n          - pattern: $X[$I + $N]\n      - metavariable-regex:\n          metavariable: $X\n          regex: \"^(keys|data|p|bytes|buf|src|dst|path|paths|segs|segments)$\"\n      # ---- Expression-level guards ----\n      # Exclude any dereference whose enclosing expression already\n      # short-circuited on a `len(X) > N` / `len(X) >= N` / `N < len(X)`\n      # / `len(X) == 0` / `len(X) < N` check (on any variable — same-block\n      # guard hygiene is what matters, not name identity).\n      - pattern-not-inside: len($G) > $N && $REST\n      - pattern-not-inside: $PRE && len($G) > $N && $REST\n      - pattern-not-inside: len($G) >= $N && $REST\n      - pattern-not-inside: $PRE && len($G) >= $N && $REST\n      - pattern-not-inside: $N < len($G) && $REST\n      - pattern-not-inside: $PRE && $N < len($G) && $REST\n      - pattern-not-inside: $N <= len($G) && $REST\n      - pattern-not-inside: len($G) == 0 || $REST\n      - pattern-not-inside: $PRE || len($G) == 0 || $REST\n      - pattern-not-inside: len($G) < $N || $REST\n      - pattern-not-inside: $PRE || len($G) < $N || $REST\n      # ---- Block-level guards ----\n      - pattern-not-inside: |\n          if len($G) > $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N < len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if $N <= len($G) {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) == 0 {\n            ...\n            return ...\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) > $N && $COND {\n            ...\n          }\n      - pattern-not-inside: |\n          if len($G) >= $N && $COND {\n            ...\n          }\n      # ---- Bounded for-loop on the slice ----\n      - pattern-not-inside: |\n          for $I := range data {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := range $D {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len(data); $I++ {\n            ...\n          }\n      - pattern-not-inside: |\n          for $I := 0; $I < len($D); $I++ {\n            ...\n          }\n</yakaai_skills>"}