# Secure coding

A request that works is not necessarily authorized. Build trusted actor context, data boundaries, parameterized queries and field allowlists together in Harbor Desk.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## Separate input from authority

Browser userId, role and tenantId are claims. Derive the actor from a server-validated session and constrain record selection by identity, tenant and object authority together. Ada/north and Ada/south are different principals. Hiding a button improves usability but cannot replace an API authorization decision.

## Query and output context

The downloadable Python example contrasts SQL concatenation with bound parameters through actual SQLite queries. Parameters prevent input from changing SQL structure; they do not authorize reading another user’s record. The WHERE clause therefore includes owner and tenant. Encode HTML output for its context separately; safe JSON does not imply safe HTML.

## Bound the change

Spreading a PATCH body into a record can overwrite owner or role. Select allowed fields, validate type and length, and reject unknown fields as the real API fixture does. Read the record again after denial to verify that data and version are unchanged. Java and C# fragments demonstrate query construction; production connection management and error policy need separate design.

## Worked example: updating a title

In the local server, choose north/ada and read t-1. Record its title and version. Retry as north/ben: expect 404. Although south/ada has the same visible local identifier, it must also be denied t-1 while retaining access to its own t-2. Return to north/ada and send title and version; adding owner must yield 400 without mutation. This separates parameter binding, field allowlisting and object authorization through observable outcomes. Run the SQL fixture only against its synthetic in-memory SQLite database; its four tests cover permitted access, injection, foreign ownership and tenant separation.

## Working sequence

1. Derive a trusted actor from the session.
2. Apply object and tenant boundaries in the query.
3. Bound the body by type, size and allowed fields.
4. Test permitted and denied paths together.
5. Verify no side effects after denial.

## 1. Who owns the object?

The weak function allows any existing record. The corrected model denies anonymous, other-user and other-tenant requests while retaining the same-tenant owner path. A denial is meaningful only when permitted use still works.

This code is a local policy model; it does not replace target/device testing.

### Weak implementation
```js
s=>Boolean(s.ticket)
```

### Corrected implementation
```js
s=>Boolean(s.actor&&s.ticket&&s.actor.tenant===s.ticket.tenant&&s.actor.id===s.ticket.owner)
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "owner",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "north"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "other-user",
      "input": {
        "actor": {
          "id": "ben",
          "tenant": "north"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "other-tenant",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "south"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "anonymous",
      "input": {
        "actor": null,
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "owner",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "north"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "other-user",
      "input": {
        "actor": {
          "id": "ben",
          "tenant": "north"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "other-tenant",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "south"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "anonymous",
      "input": {
        "actor": null,
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. Mass assignment

Owner and role must remain unchanged during a title update. The model does not copy extra fields; the downloadable API is stricter and rejects the entire request containing them. Both policies keep protected fields outside client control.

This code is a local policy model; it does not replace target/device testing.

### Weak implementation
```js
s=>({...s.record,...s.body})
```

### Corrected implementation
```js
s=>({...s.record,...(typeof s.body.title==='string'?{title:s.body.title}:{})})
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "ordinary-title",
      "input": {
        "record": {
          "title": "Old",
          "owner": "ada",
          "role": "member"
        },
        "body": {
          "title": "New"
        }
      },
      "expected": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "observed": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "matched": true
    },
    {
      "name": "privileged-fields",
      "input": {
        "record": {
          "title": "Old",
          "owner": "ada",
          "role": "member"
        },
        "body": {
          "title": "New",
          "owner": "ben",
          "role": "admin"
        }
      },
      "expected": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "observed": {
        "title": "New",
        "owner": "ben",
        "role": "admin"
      },
      "matched": false
    }
  ],
  "after": [
    {
      "name": "ordinary-title",
      "input": {
        "record": {
          "title": "Old",
          "owner": "ada",
          "role": "member"
        },
        "body": {
          "title": "New"
        }
      },
      "expected": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "observed": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "matched": true
    },
    {
      "name": "privileged-fields",
      "input": {
        "record": {
          "title": "Old",
          "owner": "ada",
          "role": "member"
        },
        "body": {
          "title": "New",
          "owner": "ben",
          "role": "admin"
        }
      },
      "expected": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "observed": {
        "title": "New",
        "owner": "ada",
        "role": "member"
      },
      "matched": true
    }
  ]
}
```

## 3. Reused local identifier

Owner comparison alone leaks data when local identifiers repeat across tenants. Include tenant equality in the same decision and isolate the boundary by changing only the tenant in the test.

This code is a local policy model; it does not replace target/device testing.

### Weak implementation
```js
s=>s.actor.id===s.ticket.owner
```

### Corrected implementation
```js
s=>s.actor.tenant===s.ticket.tenant&&s.actor.id===s.ticket.owner
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "same-tenant",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "north"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "reused-local-id",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "south"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "same-tenant",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "north"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "reused-local-id",
      "input": {
        "actor": {
          "id": "ada",
          "tenant": "south"
        },
        "ticket": {
          "owner": "ada",
          "tenant": "north"
        }
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Query, actor source and record scope
- Negative results and before/after record comparison

## References

- [OWASP ASVS 5.0.0](https://asvs.owasp.org/)
- [NIST SSDF publications: 1.1 final; 1.2 draft](https://csrc.nist.gov/projects/ssdf/publications)
