> ## Documentation Index
> Fetch the complete documentation index at: https://docs.myrqen.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Finding schema

> The submission contract, every constraint, the rejection rules, and a worked example.

Submit one finding per root cause, as JSON on stdin:

```bash theme={null}
echo '{ … }' | myrqen --json finding submit
myrqen --json finding submit --file finding.json
```

The canonical schema is
[`schemas/finding-submission.schema.json`](https://github.com/stijnswapped/Myrqen/blob/main/schemas/finding-submission.schema.json).
The CLI assigns the finding id, computes the deduplication fingerprint, redacts
secret-shaped material, normalizes the category, and records verification state.

<Warning>
  `additionalProperties` is `false`. Any extra top-level field — **including `id`** — is a
  rejection, not a warning.
</Warning>

## Required fields

| Field              | Type   | Constraints                                 | Rule                                                                               |
| ------------------ | ------ | ------------------------------------------- | ---------------------------------------------------------------------------------- |
| `title`            | string | 4–240 chars                                 | Specific and readable. Name the defect, not the category.                          |
| `category`         | string | 2–120 chars                                 | Snake case, for example `broken_object_level_authorization`. Normalized on intake. |
| `severity`         | enum   | `critical` `high` `medium` `low` `info`     | Realistic impact **in this application**.                                          |
| `verification`     | enum   | `verified` `strong_evidence` `needs_review` | How well it was proven.                                                            |
| `summary`          | string | 20–8000 chars                               | The root cause in plain language.                                                  |
| `impact`           | string | 20–8000 chars                               | What an attacker gains, in this application's terms.                               |
| `affected`         | object | see below                                   | Where the defect is.                                                               |
| `evidence`         | array  | 1–64 entries                                | At least one. See below.                                                           |
| `remediation`      | string | 20–12000 chars                              | The smallest durable fix, and the framework safeguard to prefer.                   |
| `verificationPlan` | string | 10–8000 chars                               | The check that fails before the fix and passes after it.                           |

### `affected`

| Field       | Type    | Constraints               |
| ----------- | ------- | ------------------------- |
| `component` | string  | **required**, 1–500 chars |
| `file`      | string  | ≤ 1000 chars              |
| `lineStart` | integer | ≥ 1                       |
| `lineEnd`   | integer | ≥ 1                       |
| `route`     | string  | ≤ 1000 chars              |
| `origin`    | string  | ≤ 1000 chars              |

`route` and `file` feed the deduplication fingerprint after normalization, so
`/orders/12` and `/orders/13` collapse to one location.

### `evidence[]`

| Field         | Type    | Constraints                                                                                    |
| ------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `kind`        | enum    | **required** — `source` `runtime` `identity_differential` `configuration` `dependency` `other` |
| `description` | string  | **required**, 10–12000 chars                                                                   |
| `snippet`     | string  | ≤ 24000 chars                                                                                  |
| `location`    | string  | ≤ 1000 chars                                                                                   |
| `redacted`    | boolean |                                                                                                |

## Optional fields

| Field          | Type      | Constraints                                                            |
| -------------- | --------- | ---------------------------------------------------------------------- |
| `taxonomy`     | string\[] | ≤ 16 items, each ≤ 64 chars. CWE or OWASP identifiers you can justify. |
| `reproduction` | string    | ≤ 12000 chars                                                          |
| `references`   | string\[] | ≤ 32 items, each ≤ 500 chars                                           |
| `tags`         | string\[] | ≤ 32 items, each ≤ 80 chars                                            |

## Rejection and adjustment rules

**Rejections** — the submission is not stored, and exit code is `2`:

* any extra top-level field, including `id`;
* a secret value anywhere in the payload that survives redaction;
* missing evidence;
* any schema constraint above.

**Adjustments** — the finding is stored, with a warning explaining what changed:

* `severity: "critical"` with `verification: "needs_review"` becomes `high`, because an
  unvalidated finding is a lead;
* `missing_security_header`, `verbose_error_message`, and
  `outdated_dependency_no_known_exploit_path` are capped at `medium` unless at least one
  piece of evidence has kind `runtime` or `identity_differential`.

Evidence that only restates the category is a quality failure the report will show, even
when it passes the schema.

## The submission result

```json theme={null}
{
  "accepted": true,
  "findingId": "MYR-001",
  "action": "created",
  "fingerprint": "…",
  "warnings": [],
  "errors": [],
  "redactions": 0
}
```

| Field              | Meaning                                                          |
| ------------------ | ---------------------------------------------------------------- |
| `accepted`         | Whether the finding was stored.                                  |
| `findingId`        | `MYR-001`-style id, assigned by the CLI.                         |
| `action`           | `created`, `merged_as_corroboration`, or `rejected`.             |
| `deduplicatedInto` | On a merge, the id of the finding this became corroboration for. |
| `fingerprint`      | Category + normalized location + root-cause identity.            |
| `warnings`         | Adjustments that were applied. Read them.                        |
| `errors`           | Why it was rejected.                                             |
| `redactions`       | How many pieces of secret-shaped material were redacted.         |

Submitting the same root cause again with new evidence is not a duplicate — it becomes
corroboration on the original finding.

## Worked example

```json theme={null}
{
  "title": "Order records are readable across accounts",
  "category": "broken_object_level_authorization",
  "severity": "high",
  "verification": "verified",
  "summary": "The order route resolves the record by identifier and returns it without checking that the record belongs to the authenticated subject.",
  "impact": "Any signed-in customer can read another customer's order, including delivery address and partial card data, by changing the identifier in the path.",
  "affected": {
    "component": "orders API",
    "route": "GET /api/orders/:id",
    "file": "src/routes/orders.ts",
    "lineStart": 42
  },
  "evidence": [
    {
      "kind": "identity_differential",
      "description": "Test identity user_a requested order 102, which belongs to user_b, and received HTTP 200 with the other account's record.",
      "location": "GET /api/orders/102"
    },
    {
      "kind": "source",
      "description": "The handler looks the order up by identifier alone; no ownership predicate is applied before the response.",
      "snippet": "const order = await db.orders.find(id);\nreturn Response.json(order);"
    }
  ],
  "remediation": "Scope the lookup to the authenticated subject, or verify ownership before serialising the record. Prefer making ownership part of the query rather than a separate check that can be forgotten.",
  "verificationPlan": "Repeat the cross-account request as user_a for an order owned by user_b and assert a 403 or 404 with no record body.",
  "reproduction": "curl -s -H 'Cookie: session=<user_a>' http://127.0.0.1:4010/api/orders/102",
  "taxonomy": ["CWE-639", "OWASP-API1"]
}
```

## Related schemas

Every schema in `schemas/` is language-neutral JSON Schema, and
`packages/contracts` generates its runtime validators from them — so a drift between the
schema and the validator is a lint failure, not a surprise at runtime.

| File                             | Describes                               |
| -------------------------------- | --------------------------------------- |
| `finding-submission.schema.json` | This page.                              |
| `finding.schema.json`            | A stored finding, after intake.         |
| `report.schema.json`             | The canonical `ScanReport`.             |
| `scan-event.schema.json`         | The closed live-progress event payload. |
| `share-policy.schema.json`       | A projection policy.                    |
| `project-binding.schema.json`    | The repository-to-workspace binding.    |
| `update-manifest.schema.json`    | The signed release manifest payload.    |
